diff --git a/backend/package.json b/backend/package.json index f9c9b6d..1c8646e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "license": "MIT", "dependencies": { "@sentry/node": "^5.29.2", + "@types/fluent-ffmpeg": "^2.1.21", "@types/pino": "^6.3.4", "axios": "^1.2.3", "bcryptjs": "^2.4.3", @@ -28,6 +29,7 @@ "express-async-errors": "^3.1.1", "fast-folder-size": "^1.7.0", "flat": "^5.0.2", + "fluent-ffmpeg": "^2.1.2", "fs-extra": "^10.1.0", "http-graceful-shutdown": "^2.3.2", "ioredis": "^5.2.3", @@ -42,6 +44,7 @@ "sequelize": "^5.22.3", "sequelize-cli": "^5.5.1", "sequelize-typescript": "^1.1.0", + "sharp": "^0.32.5", "socket.io": "^3.0.5", "socket.io-client": "^4.5.4", "uuid": "^8.3.2", diff --git a/backend/src/app.ts b/backend/src/app.ts index 7864346..fea4c81 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -21,7 +21,8 @@ app.use( credentials: true, origin: process.env.FRONTEND_URL }) -); +); + app.use(cookieParser()); app.use(express.json()); app.use(Sentry.Handlers.requestHandler()); diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index 798475a..e026b98 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -17,6 +17,8 @@ import { verifyMessage } from "../services/WbotServices/wbotMessageListener"; import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService"; +import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOfficialAPI"; +import Whatsapp from "../models/Whatsapp"; type IndexQuery = { pageNumber: string; @@ -27,6 +29,7 @@ type MessageData = { fromMe: boolean; read: boolean; quotedMsg?: Message; + mic_audio?: boolean }; export const index = async (req: Request, res: Response): Promise => { @@ -37,104 +40,19 @@ export const index = async (req: Request, res: Response): Promise => { pageNumber, ticketId }); - - // SetTicketMessagesAsRead(ticket); - + return res.json({ count, messages, ticket, hasMore }); }; export const store = async (req: Request, res: Response): Promise => { 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 ticket = await ShowTicketService(ticketId); 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) { await Promise.all( medias.map(async (media: Express.Multer.File) => { @@ -152,9 +70,8 @@ export const store = async (req: Request, res: Response): Promise => { media:`, media, "\n" - ); - - await SendWhatsAppMedia({ media, ticket }); + ); + await SendWhatsAppMedia({ media, ticket, mic_audio }); }) ); } else { diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 26f2657..f7a308a 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -103,7 +103,8 @@ export const index = async (req: Request, res: Response): Promise => { }; export const store = async (req: Request, res: Response): Promise => { - const { contactId, status, userId, msg, queueId }: TicketData = req.body; + const { contactId, status, userId, msg, queueId, whatsappId }: TicketData = + req.body; let ticket = await Ticket.findOne({ where: { @@ -120,7 +121,13 @@ export const store = async (req: Request, res: Response): Promise => { ticketId: ticket.id }); } else { - ticket = await CreateTicketService({ contactId, status, userId, queueId }); + ticket = await CreateTicketService({ + contactId, + status, + userId, + queueId, + whatsappId + }); } const io = getIO(); @@ -128,15 +135,6 @@ export const store = async (req: Request, res: Response): Promise => { action: "update", 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); }; @@ -243,9 +241,9 @@ export const update = async ( if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { if (ticketData.transfer) { - const defaultWhatsapp: any = await GetDefaultWhatsApp( - ticketData.userId - ); + const defaultWhatsapp: any = await GetDefaultWhatsApp({ + userId: ticketData.userId + }); const _ticket: any = await Ticket.findByPk(ticketId); @@ -276,8 +274,6 @@ export const update = async ( await setMessageAsRead(ticket); } - console.log("ticket.unreadMessages: ", ticket.unreadMessages); - if (ticketData.userId) { const dateToday = splitDateTime( new Date(format(new Date(), "yyyy-MM-dd HH:mm:ss", { locale: ptBR })) diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 0f1252a..0a73278 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -16,19 +16,25 @@ import AppError from "../errors/AppError"; import getNumberFromName from "../helpers/GetNumberSequence"; import phoneNumberStart from "../helpers/PhoneNumberStatusCode"; -import path from "path"; +import path, { join } from "path"; import validatePhoneName from "../helpers/ValidatePhoneName"; import postData from "../helpers/AxiosPost"; 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"; interface WhatsappData { name: string; @@ -47,12 +53,113 @@ export const index = async (req: Request, res: Response): Promise => { return res.status(200).json(whatsapps); }; +export const whatsAppOfficialMatchQueue = async ( + req: Request, + res: Response +): Promise => { + 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 => { + 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 => { // 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 ( @@ -62,37 +169,71 @@ export const weebhook = async ( req.body.entry[0].changes[0].value.messages && 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 msg_body = req.body.entry[0].changes[0].value.messages[0].text.body; // extract the message text from the webhook payload - const type = req.body.entry[0].changes[0].value.messages[0].type; + 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; - console.log("from: ", contact_from); - 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 wbot = {}; let msg = {}; + let contacts = req.body.entry[0].changes[0].value.contacts[0]; msg = { ...msg, - id: { id: req.body.entry[0].changes[0].value.messages[0].id }, + id: { id: message.id }, fromMe: false, - type: type === "text" ? "chat" : type, + type: type, 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); } @@ -266,5 +407,3 @@ export const remove = async ( return res.status(200).json({ message: "Whatsapp deleted." }); }; - - diff --git a/backend/src/database/migrations/20230828204358-add-column-whatsapp-phone-number-id.ts b/backend/src/database/migrations/20230828204358-add-column-whatsapp-phone-number-id.ts new file mode 100644 index 0000000..dfff7c1 --- /dev/null +++ b/backend/src/database/migrations/20230828204358-add-column-whatsapp-phone-number-id.ts @@ -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"); + } +}; diff --git a/backend/src/database/migrations/20230823211123-add-column-whatsapp-official.ts b/backend/src/database/migrations/20230829131253-add-column-tickets-phone-number-id.ts similarity index 50% rename from backend/src/database/migrations/20230823211123-add-column-whatsapp-official.ts rename to backend/src/database/migrations/20230829131253-add-column-tickets-phone-number-id.ts index 4f7f301..3197236 100644 --- a/backend/src/database/migrations/20230823211123-add-column-whatsapp-official.ts +++ b/backend/src/database/migrations/20230829131253-add-column-tickets-phone-number-id.ts @@ -2,13 +2,13 @@ import { QueryInterface, DataTypes } from "sequelize"; module.exports = { up: (queryInterface: QueryInterface) => { - return queryInterface.addColumn("Whatsapps", "official", { - type: DataTypes.BOOLEAN, - defaultValue: false + return queryInterface.addColumn("Tickets", "phoneNumberId", { + type: DataTypes.STRING, + allowNull: true }); }, down: (queryInterface: QueryInterface) => { - return queryInterface.removeColumn("Whatsapps", "official"); + return queryInterface.removeColumn("Tickets", "phoneNumberId"); } }; diff --git a/backend/src/database/migrations/20230830131300-add-column-messages-phone-number-id.ts b/backend/src/database/migrations/20230830131300-add-column-messages-phone-number-id.ts new file mode 100644 index 0000000..889a825 --- /dev/null +++ b/backend/src/database/migrations/20230830131300-add-column-messages-phone-number-id.ts @@ -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"); + } +}; diff --git a/backend/src/helpers/BytesToMB.ts b/backend/src/helpers/BytesToMB.ts new file mode 100644 index 0000000..b677baa --- /dev/null +++ b/backend/src/helpers/BytesToMB.ts @@ -0,0 +1,3 @@ +export function bytesToMB(bytes: number | string) { + return (+bytes / (1024 * 1024)).toFixed(2); +} diff --git a/backend/src/helpers/ConvertAudio.ts b/backend/src/helpers/ConvertAudio.ts new file mode 100644 index 0000000..f8c7f55 --- /dev/null +++ b/backend/src/helpers/ConvertAudio.ts @@ -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 { + 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 { + return new Promise((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 }; diff --git a/backend/src/helpers/GetDefaultWhatsApp.ts b/backend/src/helpers/GetDefaultWhatsApp.ts index 4470654..5af78d5 100644 --- a/backend/src/helpers/GetDefaultWhatsApp.ts +++ b/backend/src/helpers/GetDefaultWhatsApp.ts @@ -1,55 +1,66 @@ import AppError from "../errors/AppError"; import Whatsapp from "../models/Whatsapp"; -import WhatsappQueue from "../models/WhatsappQueue" -import UserQueue from "../models/UserQueue" +import WhatsappQueue from "../models/WhatsappQueue"; +import UserQueue from "../models/UserQueue"; import { Op, where } from "sequelize"; -import wbotByUserQueue from '../helpers/GetWbotByUserQueue' +import wbotByUserQueue from "../helpers/GetWbotByUserQueue"; // import WhatsQueueIndex from "./WhatsQueueIndex"; import { WhatsIndex } from "./LoadBalanceWhatsSameQueue"; -const GetDefaultWhatsApp = async (userId?: string | number): Promise => { +interface Request { + userId?: string | number; + queueId?: string | number; +} +//const GetDefaultWhatsApp = async (userId?: string | number): Promise => { + +const GetDefaultWhatsApp = async ({ + userId, + queueId +}: Request): Promise => { // test del let defaultWhatsapp = await Whatsapp.findOne({ where: { isDefault: true } }); if (!defaultWhatsapp) { - - 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 (whatsapps.length > 0) { - - if (whatsapps.length > 1) { - - 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) { @@ -58,20 +69,6 @@ const GetDefaultWhatsApp = async (userId?: string | number): Promise = 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; diff --git a/backend/src/helpers/GetTicketWbot.ts b/backend/src/helpers/GetTicketWbot.ts index d4cf471..367f519 100644 --- a/backend/src/helpers/GetTicketWbot.ts +++ b/backend/src/helpers/GetTicketWbot.ts @@ -7,7 +7,7 @@ const GetTicketWbot = async (ticket: Ticket): Promise => { if (!ticket.whatsappId) { - const defaultWhatsapp = await GetDefaultWhatsApp(); + const defaultWhatsapp = await GetDefaultWhatsApp({}); await ticket.$set("whatsapp", defaultWhatsapp); } diff --git a/backend/src/helpers/GetWbotByUserQueue.ts b/backend/src/helpers/GetWbotByUserQueue.ts index d444cb7..0b4f23f 100644 --- a/backend/src/helpers/GetWbotByUserQueue.ts +++ b/backend/src/helpers/GetWbotByUserQueue.ts @@ -1,50 +1,64 @@ - import UserQueue from "../models/UserQueue"; import WhatsappQueue from "../models/WhatsappQueue"; import Whatsapp from "../models/Whatsapp"; import { Op, where } from "sequelize"; -const wbotByUserQueue = async (userId: string | number, status: string = 'CONNECTED') => { - - let defaultWhatsapp: Whatsapp[] = [] - - 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; - +interface Request { + userId: string | number; + status?: string; + queueId?: string | number; } -export default wbotByUserQueue; +const wbotByUserQueue = async ({ + userId, + status = "CONNECTED", + queueId +}: Request): Promise => { + 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; diff --git a/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts b/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts new file mode 100644 index 0000000..fec1aa9 --- /dev/null +++ b/backend/src/helpers/ReceiveWhatsAppMediaOfficialAPI.ts @@ -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; diff --git a/backend/src/helpers/SendWhatsappMessageSocket.ts b/backend/src/helpers/SendWhatsappMessageSocket.ts index c9485f3..1998a50 100644 --- a/backend/src/helpers/SendWhatsappMessageSocket.ts +++ b/backend/src/helpers/SendWhatsappMessageSocket.ts @@ -1,21 +1,33 @@ import { getIO } from "../libs/socket"; import Ticket from "../models/Ticket"; - +import sendWhatsAppMessageOfficialAPI from "./sendWhatsAppMessageOfficialAPI" -function sendWhatsAppMessageSocket(ticket: Ticket, body: string, quotedMsgSerializedId?: string | undefined, number?: string ) { +function sendWhatsAppMessageSocket( + ticket: Ticket, + body: string, + quotedMsgSerializedId?: string | undefined, + number?: string +) { + const { phoneNumberId } = ticket; - const io = getIO(); + if (phoneNumberId) { + sendWhatsAppMessageOfficialAPI(ticket, body, quotedMsgSerializedId); + return; + } - 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 - } - }); + 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 + } + }); } -export default sendWhatsAppMessageSocket; \ No newline at end of file +export default sendWhatsAppMessageSocket; diff --git a/backend/src/helpers/SetMessageAsRead.ts b/backend/src/helpers/SetMessageAsRead.ts index 3f073fa..d29d297 100644 --- a/backend/src/helpers/SetMessageAsRead.ts +++ b/backend/src/helpers/SetMessageAsRead.ts @@ -1,12 +1,27 @@ +import { Op } from "sequelize" import { getWbot } from "../libs/wbot"; +import Message from "../models/Message" import Ticket from "../models/Ticket"; +import Whatsapp from "../models/Whatsapp"; import endPointQuery from "./old_EndPointQuery"; +import whatsappOfficialAPI from "./WhatsappOfficialAPI"; + export async function setMessageAsRead(ticket: Ticket) { - - const wbot_url = await getWbot(ticket.whatsappId); + if (ticket?.phoneNumberId) { + return; + } - console.log('from wbotMessagelistener wbot_url: ', wbot_url, ' | ticket.contact.number: ', ticket.contact.number); + const wbot_url = await getWbot(ticket.whatsappId); - await endPointQuery(`${wbot_url}/api/sendSeen`, { number: `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` }); -} \ No newline at end of file + 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` + }); +} diff --git a/backend/src/helpers/WhatsappOfficialAPI.ts b/backend/src/helpers/WhatsappOfficialAPI.ts index 6880a4f..2bc4703 100644 --- a/backend/src/helpers/WhatsappOfficialAPI.ts +++ b/backend/src/helpers/WhatsappOfficialAPI.ts @@ -1,13 +1,10 @@ import axios from "axios"; -const token = - "EAADwEiQkJqABOZBOuvnVZAywgKUw8wPETCcleXWDqKN3X2W1LZC5UMEnSjBIVjBavZBxwVTD4WnpDmoEFN9HZCOt842XZCTSPm1ShnnUB2iJca3nZC6gS8GLIKqP78kkW5EtllhWrg4I8JnbllrLNKa2B066Wwh0G3uySYgcA7WnKyZAmPOGEZB8UiljBaqhg"; - const api = axios.create({ - baseURL: "https://graph.facebook.com", + baseURL: process.env.URL_WHATSAPP_API, headers: { Accept: "application/json", - Authorization: `Bearer ${token}` + Authorization: `Bearer ${process.env.TOKEN}` } }); diff --git a/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts new file mode 100644 index 0000000..93e4344 --- /dev/null +++ b/backend/src/helpers/sendWhatsAppMessageOfficialAPI.ts @@ -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; diff --git a/backend/src/helpers/sendWhatsMediaOfficialAPI.ts b/backend/src/helpers/sendWhatsMediaOfficialAPI.ts new file mode 100644 index 0000000..d2c6390 --- /dev/null +++ b/backend/src/helpers/sendWhatsMediaOfficialAPI.ts @@ -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; diff --git a/backend/src/models/Message.ts b/backend/src/models/Message.ts index 65f05cf..40fe9f8 100644 --- a/backend/src/models/Message.ts +++ b/backend/src/models/Message.ts @@ -34,10 +34,15 @@ class Message extends Model { @Column(DataType.TEXT) body: string; + @Column + phoneNumberId: string; + @Column(DataType.STRING) get mediaUrl(): string | null { 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; } diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index 0cd6888..d3b95c9 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -45,6 +45,9 @@ class Ticket extends Model { @Column statusChatEnd: string; + @Column + phoneNumberId: string; + @CreatedAt createdAt: Date; diff --git a/backend/src/models/Whatsapp.ts b/backend/src/models/Whatsapp.ts index 2e60576..c98e622 100644 --- a/backend/src/models/Whatsapp.ts +++ b/backend/src/models/Whatsapp.ts @@ -58,10 +58,13 @@ class Whatsapp extends Model { @Column url: string; - + @Column urlApi: string; + @Column + phoneNumberId: string; + @Default(false) @AllowNull @Column diff --git a/backend/src/routes/whatsappRoutes.ts b/backend/src/routes/whatsappRoutes.ts index 215ede3..93dff6a 100644 --- a/backend/src/routes/whatsappRoutes.ts +++ b/backend/src/routes/whatsappRoutes.ts @@ -9,8 +9,22 @@ whatsappRoutes.get("/whatsapp/", isAuth, WhatsAppController.index); 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.put("/whatsapp/:whatsappId", isAuth, WhatsAppController.update); diff --git a/backend/src/server.ts b/backend/src/server.ts index b5002e8..3b1cfd8 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -43,13 +43,17 @@ gracefulShutdown(server); loadSettings(); - let whatsapps: any = await Whatsapp.findAll({ attributes: ["id", "url"] }); - - // console.log('whatsapps: ', whatsapps) + let whatsapps: any = await Whatsapp.findAll({ + attributes: ["id", "url", "phoneNumberId"] + }); if (whatsapps && whatsapps.length > 0) { for (let i = 0; i < whatsapps.length; i++) { try { + const { phoneNumberId } = whatsapps[i]; + + if (phoneNumberId) continue; + console.log( `API URL: ${whatsapps[i].dataValues.url}/api/connection/status` ); diff --git a/backend/src/services/MessageServices/CreateMessageService.ts b/backend/src/services/MessageServices/CreateMessageService.ts index 5c8493e..95af0e3 100644 --- a/backend/src/services/MessageServices/CreateMessageService.ts +++ b/backend/src/services/MessageServices/CreateMessageService.ts @@ -23,6 +23,7 @@ const CreateMessageService = async ({ }: Request): Promise => { try { + await Message.upsert(messageData); const message = await Message.findByPk(messageData.id, { diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index bfcd8fa..36dc292 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -15,6 +15,7 @@ import TicketEmiterSumOpenClosedByUser from "../../helpers/OnlineReporEmiterInfo import { createOrUpdateTicketCache } from "../../helpers/TicketCache"; import User from "../../models/User"; import whatsappQueueMatchingUserQueue from "../../helpers/whatsappQueueMatchingUserQueue"; +import Whatsapp from "../../models/Whatsapp"; let flatten = require("flat"); interface Request { @@ -22,18 +23,29 @@ interface Request { status: string; userId: number; queueId?: number | undefined; + whatsappId?: number | string; } const CreateTicketService = async ({ contactId, status, userId, - queueId = undefined + queueId = undefined, + whatsappId }: Request): Promise => { - console.log("========> queueId: ", queueId); - 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 }); @@ -56,7 +68,8 @@ const CreateTicketService = async ({ status, isGroup, userId, - queueId + queueId, + phoneNumberId }); const ticket = await Ticket.findByPk(id, { include: ["contact"] }); diff --git a/backend/src/services/TicketServices/FindOrCreateTicketService.ts b/backend/src/services/TicketServices/FindOrCreateTicketService.ts index 614d98a..66d51a7 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketService.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketService.ts @@ -13,14 +13,14 @@ const FindOrCreateTicketService = async ( contact: Contact, whatsappId: number, unreadMessages: number, - groupContact?: Contact + groupContact?: Contact, ): Promise => { try { let ticket; // else if (getSettingValue("whatsaAppCloudApi")?.value == "enabled") { - - // } + + // } if (getSettingValue("oneContactChatWithManyWhats")?.value == "enabled") { let whats = await ListWhatsAppsNumber(whatsappId); @@ -45,7 +45,7 @@ const FindOrCreateTicketService = async ( }); } - const { queues, greetingMessage } = await ShowWhatsAppService(whatsappId); + const { queues, greetingMessage, phoneNumberId } = await ShowWhatsAppService(whatsappId); const botInfo = { isOnQueue: false }; @@ -106,7 +106,8 @@ const FindOrCreateTicketService = async ( status: status, isGroup: !!groupContact, unreadMessages, - whatsappId + whatsappId, + phoneNumberId }); } diff --git a/backend/src/services/TicketServices/UpdateTicketService.ts b/backend/src/services/TicketServices/UpdateTicketService.ts index 9c6ad43..42619f8 100644 --- a/backend/src/services/TicketServices/UpdateTicketService.ts +++ b/backend/src/services/TicketServices/UpdateTicketService.ts @@ -47,8 +47,7 @@ const UpdateTicketService = async ({ whatsappId } = ticketData; - const ticket = await ShowTicketService(ticketId); - // await SetTicketMessagesAsRead(ticket); + const ticket = await ShowTicketService(ticketId); const oldStatus = ticket.status; const oldUserId = ticket.user?.id; diff --git a/backend/src/services/WbotServices/CheckIsValidContact.ts b/backend/src/services/WbotServices/CheckIsValidContact.ts index e26e525..888146a 100644 --- a/backend/src/services/WbotServices/CheckIsValidContact.ts +++ b/backend/src/services/WbotServices/CheckIsValidContact.ts @@ -5,7 +5,7 @@ import { getWbot } from "../../libs/wbot"; const CheckIsValidContact = async (number: string): Promise => { - const defaultWhatsapp = await GetDefaultWhatsApp(); + const defaultWhatsapp = await GetDefaultWhatsApp({}); const wbot_url = await getWbot(defaultWhatsapp.id); diff --git a/backend/src/services/WbotServices/GetProfilePicUrl.ts b/backend/src/services/WbotServices/GetProfilePicUrl.ts index 561e79f..82be7d2 100644 --- a/backend/src/services/WbotServices/GetProfilePicUrl.ts +++ b/backend/src/services/WbotServices/GetProfilePicUrl.ts @@ -4,19 +4,15 @@ import { getWbot } from "../../libs/wbot"; const GetProfilePicUrl = async (number: string): Promise => { - const defaultWhatsapp = await GetDefaultWhatsApp(); + const defaultWhatsapp = await GetDefaultWhatsApp({}); - const wbot_url = await getWbot(defaultWhatsapp.id); - - // const profilePicUrl = await wbot.getProfilePicUrl(`${number}@c.us`); + const wbot_url = await getWbot(defaultWhatsapp.id); let profilePicUrl = await endPointQuery(`${wbot_url}/api/GetProfilePicUrl`, { number: `${number}`, }) console.log('profilePicUrl.data.data: ', profilePicUrl.data.data) - if (profilePicUrl && profilePicUrl.data.data) { - - console.log('GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG') + if (profilePicUrl && profilePicUrl.data.data) { return profilePicUrl.data.data; } diff --git a/backend/src/services/WbotServices/SendWhatsAppMedia.ts b/backend/src/services/WbotServices/SendWhatsAppMedia.ts index f15a9cb..9f439bd 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMedia.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMedia.ts @@ -4,47 +4,61 @@ import AppError from "../../errors/AppError"; import GetTicketWbot from "../../helpers/GetTicketWbot"; import Ticket from "../../models/Ticket"; -import { updateTicketCacheByTicketId } from '../../helpers/TicketCache' +import { updateTicketCacheByTicketId } from "../../helpers/TicketCache"; import { date } from "faker"; import { getIO } from "../../libs/socket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; import sendWhatsAppMediaSocket from "../../helpers/SendWhatsappMessageMediaSocket"; +import sendWhatsMediaOfficialAPI from "../../helpers/sendWhatsMediaOfficialAPI"; +import { mediaTypeWhatsappOfficial } from "./wbotMessageListener"; +import { bytesToMB } from "../../helpers/BytesToMB"; interface Request { media: Express.Multer.File; - ticket: Ticket; + ticket: Ticket; + mic_audio?: any } const SendWhatsAppMedia = async ({ media, - ticket + ticket, + mic_audio }: Request): Promise => { - try { - // const wbot = await GetTicketWbot(ticket); + 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 { 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 }); - // TEST DEL - await updateTicketCacheByTicketId(ticket.id, { lastMessage: media.filename, updatedAt: new Date(ticket.updatedAt).toISOString() }) - // + await updateTicketCacheByTicketId(ticket.id, { + 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); - - // return sentMessage; - } catch (err) { throw new AppError("ERR_SENDING_WAPP_MSG"); } }; export default SendWhatsAppMedia; - - - diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index 183aaf3..767af76 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -8,11 +8,14 @@ import Ticket from "../../models/Ticket"; import Whatsapp from "../../models/Whatsapp"; import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; -import wbotByUserQueue from '../../helpers/GetWbotByUserQueue' +import wbotByUserQueue from "../../helpers/GetWbotByUserQueue"; import { WhatsIndex } from "../../helpers/LoadBalanceWhatsSameQueue"; -import { deleteTicketsByContactsCache, updateTicketCacheByTicketId } from '../../helpers/TicketCache' +import { + deleteTicketsByContactsCache, + updateTicketCacheByTicketId +} from "../../helpers/TicketCache"; import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"; import { getWbot } from "../../libs/wbot"; @@ -26,14 +29,13 @@ import autoRestore from "../../helpers/AutoRestore"; import { _restore } from "../../helpers/RestoreControll"; import { getIO } from "../../libs/socket"; import sendWhatsAppMessageSocket from "../../helpers/SendWhatsappMessageSocket"; - - +import sendWhatsAppMessageOfficialAPI from "../../helpers/sendWhatsAppMessageOfficialAPI"; interface Request { body: string; ticket: Ticket; quotedMsg?: Message; - number?: string + number?: string; } const SendWhatsAppMessage = async ({ @@ -42,112 +44,109 @@ const SendWhatsAppMessage = async ({ quotedMsg, number }: Request): Promise => { - try { + const { phoneNumberId } = ticket; - // let timestamp = Math.floor(Date.now() / 1000) - let timestamp = Date.now() + String(Math.floor(Math.random() * 1000)) + if (phoneNumberId) { + 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`; - console.time(timetaken) - + console.time(timetaken); let quotedMsgSerializedId: string | undefined; if (quotedMsg) { - await GetWbotMessage(ticket, quotedMsg.id); 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) { - - const defaultWhatsapp: any = await GetDefaultWhatsApp(ticket.userId); + const defaultWhatsapp: any = await GetDefaultWhatsApp({ + userId: ticket.userId + }); await ticket.update({ whatsappId: +defaultWhatsapp.id }); - } 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) { - - await ticket.update({ whatsappId: + listWhatsapp.whatsapps[0].id }); - + if ( + listWhatsapp.whatsapp && + listWhatsapp.whatsapp.status != "CONNECTED" && + listWhatsapp.whatsapps.length > 0 + ) { + await ticket.update({ whatsappId: +listWhatsapp.whatsapps[0].id }); } - if (listWhatsapp.whatsapps.length > 1) { - - const _whatsapp = listWhatsapp.whatsapps[Math.floor(Math.random() * listWhatsapp.whatsapps.length)]; + const _whatsapp = + listWhatsapp.whatsapps[ + Math.floor(Math.random() * listWhatsapp.whatsapps.length) + ]; 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 > 1) { - - await ticket.update({ whatsappId: whatsapps[+WhatsIndex(whatsapps)].id }); - - } - else { - + await ticket.update({ + whatsappId: whatsapps[+WhatsIndex(whatsapps)].id + }); + } else { 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 { - 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() }) - - console.timeEnd(timetaken) + await updateTicketCacheByTicketId(ticket.id, { + lastMessage: body, + updatedAt: new Date(ticket.updatedAt).toISOString() + }); + console.timeEnd(timetaken); } 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"); - } - } 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); } - }; export default SendWhatsAppMessage; - - diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 3673625..3aa8ec4 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -110,7 +110,9 @@ const verifyContact = async ( }; } else { contactData = { - name: whatsAppOfficial.number, + name: whatsAppOfficial?.name + ? whatsAppOfficial.name + : whatsAppOfficial.number, number: whatsAppOfficial.number, isGroup: false }; @@ -151,22 +153,6 @@ const verifyMediaMessage = async ( throw new Error("ERR_WAPP_DOWNLOAD_MEDIA"); } - if (!media.filename) { - 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" - ); - } catch (err) { - Sentry.captureException(err); - logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); - } - const messageData = { id: msg.id.id, ticketId: ticket.id, @@ -177,9 +163,26 @@ const verifyMediaMessage = async ( mediaUrl: media.filename, mediaType: media.mimetype.split("/")[0], quotedMsgId: quotedMsg - // quotedMsgId: quotedMsg?.id }; + if (!ticket?.phoneNumberId) { + if (!media.filename) { + 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" + ); + } catch (err) { + Sentry.captureException(err); + logger.error(`There was an error: wbotMessageLitener.ts: ${err}`); + } + } + await ticket.update({ lastMessage: msg.body || media.filename }); const newMessage = await CreateMessageService({ messageData }); @@ -200,7 +203,8 @@ const verifyMessage = async ( fromMe: msg.fromMe, mediaType: msg.type, read: msg.fromMe, - quotedMsgId: quotedMsg + quotedMsgId: quotedMsg, + phoneNumberId: msg?.phoneNumberId }; await ticket.update({ lastMessage: msg.body }); @@ -273,9 +277,6 @@ const verifyQueue = async ( body = `\u200e${choosenQueue.greetingMessage}`; } - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); - // await verifyMessage(sentMessage, ticket, contact); - sendWhatsAppMessageSocket(ticket, body); } else { //test del transfere o atendimento se entrar na ura infinita @@ -302,14 +303,13 @@ const verifyQueue = async ( const body = `\u200e${greetingMessage}\n${options}`; + const { phoneNumberId } = ticket; + const debouncedSentMessage = debounce( async () => { - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); - // verifyMessage(sentMessage, ticket, contact); - sendWhatsAppMessageSocket(ticket, body); }, - 3000, + phoneNumberId ? 0 : 3000, 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 => { if (msg.from === "status@broadcast") return false; if ( @@ -363,43 +408,19 @@ const botTransferTicket = async ( }; const botSendMessage = (ticket: Ticket, msg: string) => { + const { phoneNumberId } = ticket; + const debouncedSentMessage = debounce( async () => { - //OLD - // const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, `${msg}`); - // verifyMessage(sentMessage, ticket, contact); - - //NEW await SendWhatsAppMessage({ body: msg, ticket }); }, - 3000, + phoneNumberId ? 0 : 3000, ticket.id ); 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 = () => { console.log("THE lst.length: ", lst.length); @@ -414,8 +435,12 @@ const _clear_lst = () => { setWhatsappId(whatsappIdsSplited, true); }; -const handleMessage = async (msg: any, wbot: any): Promise => { - if (!msg.fromMe) { +const handleMessage = async ( + msg: any, + wbot: any, + whatsAppOfficial?: any +): Promise => { + if (!msg.fromMe && !whatsAppOfficial) { _clear_lst(); let index = lst.findIndex((x: any) => x.id == msg.id.id); @@ -502,7 +527,12 @@ const handleMessage = async (msg: any, wbot: any): Promise => { 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 ( unreadMessages === 0 && @@ -653,6 +683,11 @@ const handleMsgAck = async ( ); return; } + + console.log("messageToUpdate.ack: ", messageToUpdate.ack, " | ack: ", ack); + + if (messageToUpdate.ack > ack) return; + await messageToUpdate.update({ ack }); io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { @@ -685,6 +720,8 @@ export { handleMsgAck, lst, verifyMessage, + verifyMediaMessage, verifyContact, - isValidMsg, + isValidMsg, + mediaTypeWhatsappOfficial }; diff --git a/backend/src/services/WhatsappService/ShowWhatsAppService.ts b/backend/src/services/WhatsappService/ShowWhatsAppService.ts index 866725d..fbbef03 100644 --- a/backend/src/services/WhatsappService/ShowWhatsAppService.ts +++ b/backend/src/services/WhatsappService/ShowWhatsAppService.ts @@ -2,19 +2,46 @@ import Whatsapp from "../../models/Whatsapp"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; -const ShowWhatsAppService = async (id: string | number): Promise => { - const whatsapp = await Whatsapp.findByPk(id, { - include: [ - { - model: Queue, - as: "queues", - attributes: ["id", "name", "color", "greetingMessage"] - } - ], - order: [["queues", "id", "ASC"]] - }); +const ShowWhatsAppService = async ( + id: string | number | null, + whatsAppOfficial?: any +): Promise => { + let whatsapp; - // 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) { throw new AppError("ERR_NO_WAPP_FOUND", 404); diff --git a/frontend/src/components/ContactCreateTicketModal/index.js b/frontend/src/components/ContactCreateTicketModal/index.js index 55fa06b..a2fb86a 100644 --- a/frontend/src/components/ContactCreateTicketModal/index.js +++ b/frontend/src/components/ContactCreateTicketModal/index.js @@ -1,27 +1,30 @@ -import React, { useState, useEffect, useContext, useRef, useCallback } from "react"; -import { useHistory } from "react-router-dom"; -import { toast } from "react-toastify"; +import React, { useState, useEffect, useContext, useRef, useCallback } from "react" +import { useHistory } from "react-router-dom" +import { toast } from "react-toastify" -import Button from "@material-ui/core/Button"; -import Dialog from "@material-ui/core/Dialog"; -import Select from "@material-ui/core/Select"; -import FormControl from "@material-ui/core/FormControl"; -import InputLabel from "@material-ui/core/InputLabel"; -import MenuItem from "@material-ui/core/MenuItem"; -import LinearProgress from "@material-ui/core/LinearProgress"; -import { makeStyles } from "@material-ui/core"; +import Button from "@material-ui/core/Button" +import Dialog from "@material-ui/core/Dialog" +import Select from "@material-ui/core/Select" +import FormControl from "@material-ui/core/FormControl" +import InputLabel from "@material-ui/core/InputLabel" +import MenuItem from "@material-ui/core/MenuItem" +import LinearProgress from "@material-ui/core/LinearProgress" +import { makeStyles } from "@material-ui/core" -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogActions from "@material-ui/core/DialogActions" +import DialogContent from "@material-ui/core/DialogContent" +import DialogTitle from "@material-ui/core/DialogTitle" -import { i18n } from "../../translate/i18n"; -import ButtonWithSpinner from "../ButtonWithSpinner"; -import { AuthContext } from "../../context/Auth/AuthContext"; +import { i18n } from "../../translate/i18n" +import ButtonWithSpinner from "../ButtonWithSpinner" +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) => ({ maxWidth: { @@ -33,30 +36,64 @@ const useStyles = makeStyles((theme) => ({ linearProgress: { marginTop: "5px" } -})); +})) const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { - const { user } = useContext(AuthContext); + const { user } = useContext(AuthContext) + const { whatsApps } = useContext(WhatsAppsContext) + let isMounted = useRef(true) - const history = useHistory(); - const [queues, setQueues] = useState([]); - const [loading, setLoading] = useState(false); - const [selectedQueue, setSelectedQueue] = useState(''); + const history = useHistory() + const [queues, setQueues] = useState([]) + const [loading, setLoading] = useState(false) + const [selectedQueue, setSelectedQueue] = useState('') 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(() => { - const userQueues = user.queues.map(({ id, name, color }) => { return { id, name, color } }) - if (userQueues.length === 1) setSelectedQueue(userQueues[0].id) - setQueues(userQueues) - }, [user]); + + const delayDebounceFn = setTimeout(() => { + + 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 = () => { - 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) { console.log("Missing contactId or userId") return @@ -65,8 +102,12 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { toast.warning("Nenhuma Fila Selecionada") return } - if (isMounted.current) setLoading(true); - + if (isMounted.current) setLoading(true) + + if (whatsQueue && !selectedWhatsId) { + toast.warn('Selecione um numero!') + return + } const delayDebounceFn = setTimeout(() => { const ticketCreate = async () => { @@ -76,32 +117,79 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { userId: userId, queueId: queueId, status: "open", - }); - history.push(`/tickets/${ticket.id}`); - } catch (err) { - toastError(err); - } - if (isMounted.current) setLoading(false); + whatsappId: selectedWhatsId + }) + history.push(`/tickets/${ticket.id}`) - }; - ticketCreate(); - }, 300); - return () => clearTimeout(delayDebounceFn); + setWhatsQueue(null) + setSelectedWhatsId(null) + } catch (err) { + toastError(err) + } + if (isMounted.current) setLoading(false) + + } + ticketCreate() + }, 300) + return () => clearTimeout(delayDebounceFn) }, [history]) 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) { - return - } + if (selectedQueue && selectedQueue.length === 0 || !selectedQueue) { + 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 + // } return ( @@ -117,9 +205,10 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { label={i18n.t("Filas")} >   - {queues.map(({ id, color, name }) => ( + {queues.map(({ id, color, name, disable }) => ( setItemHover(id)} onMouseLeave={() => setItemHover(-1)} @@ -131,6 +220,31 @@ const ContactCreateTicketModal = ({ modalOpen, onClose, contactId }) => { + + {whatsQueue && Array.isArray(whatsQueue) ? + + + {i18n.t("Selecionar Numero")} + + + + : <> + } + + + handleSaveTicket(contactId, user.id, selectedQueue)} + onClick={() => handleSaveTicket(contactId, user.id, selectedQueue, selectedWhatsId, whatsQueue, queues)} variant="contained" color="primary" loading={loading} + disabled={disabled} > {i18n.t("newTicketModal.buttons.ok")} - ); -}; + ) +} -export default ContactCreateTicketModal; \ No newline at end of file +export default ContactCreateTicketModal \ No newline at end of file diff --git a/frontend/src/components/MessageInput/index.js b/frontend/src/components/MessageInput/index.js index 59ba149..6acfb4e 100644 --- a/frontend/src/components/MessageInput/index.js +++ b/frontend/src/components/MessageInput/index.js @@ -388,6 +388,7 @@ const MessageInput = ({ ticketStatus }) => { formData.append("medias", blob, filename); formData.append("body", filename); formData.append("fromMe", true); + formData.append("mic_audio", true) await api.post(`/messages/${ticketId}`, formData); } catch (err) { diff --git a/frontend/src/components/MessageOptionsMenu/index.js b/frontend/src/components/MessageOptionsMenu/index.js index ea0dd0b..feca89c 100644 --- a/frontend/src/components/MessageOptionsMenu/index.js +++ b/frontend/src/components/MessageOptionsMenu/index.js @@ -56,7 +56,7 @@ const MessageOptionsMenu = ({ message, menuOpen, handleClose, anchorEl }) => { onClose={handleClose} > {message.fromMe && ( - + {i18n.t("messageOptionsMenu.delete")} )} diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index 8e86ea7..34ce098 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -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 { format } from "date-fns"; -import openSocket from "socket.io-client"; -import useSound from "use-sound"; +import { useHistory } from "react-router-dom" +import { format } from "date-fns" +import openSocket from "socket.io-client" +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 List from "@material-ui/core/List"; -import ListItem from "@material-ui/core/ListItem"; -import ListItemText from "@material-ui/core/ListItemText"; -import { makeStyles } from "@material-ui/core/styles"; +import List from "@material-ui/core/List" +import ListItem from "@material-ui/core/ListItem" +import ListItemText from "@material-ui/core/ListItemText" +import { makeStyles } from "@material-ui/core/styles" //import Badge from "@material-ui/core/Badge"; //import ChatIcon from "@material-ui/icons/Chat"; -import TicketListItem from "../TicketListItem"; -import { i18n } from "../../translate/i18n"; -import useTickets from "../../hooks/useTickets"; -import alertSound from "../../assets/sound.mp3"; -import { AuthContext } from "../../context/Auth/AuthContext"; +import TicketListItem from "../TicketListItem" +import { i18n } from "../../translate/i18n" +import useTickets from "../../hooks/useTickets" +import alertSound from "../../assets/sound.mp3" +import { AuthContext } from "../../context/Auth/AuthContext" const useStyles = makeStyles(theme => ({ tabContainer: { @@ -38,7 +38,7 @@ const useStyles = makeStyles(theme => ({ noShadow: { boxShadow: "none !important", }, -})); +})) let _fifo @@ -64,82 +64,76 @@ let _fifo const NotificationsPopOver = () => { - const classes = useStyles(); + const classes = useStyles() - const history = useHistory(); - const { user } = useContext(AuthContext); - const ticketIdUrl = +history.location.pathname.split("/")[2]; - const ticketIdRef = useRef(ticketIdUrl); - const anchorEl = useRef(); - const [isOpen, setIsOpen] = useState(false); - const [notifications, setNotifications] = useState([]); + const history = useHistory() + const { user } = useContext(AuthContext) + const ticketIdUrl = +history.location.pathname.split("/")[2] + const ticketIdRef = useRef(ticketIdUrl) + const anchorEl = useRef() + const [isOpen, setIsOpen] = useState(false) + const [notifications, setNotifications] = useState([]) - const [, setDesktopNotifications] = useState([]); + const [, setDesktopNotifications] = useState([]) - const { tickets } = useTickets({ withUnreadMessages: "true" }); - const [play] = useSound(alertSound); - const soundAlertRef = useRef(); + const { tickets } = useTickets({ withUnreadMessages: "true" }) + const [play] = useSound(alertSound) + 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]) - - - + useEffect(() => { - soundAlertRef.current = play; + soundAlertRef.current = play if (!("Notification" in window)) { } else { - Notification.requestPermission(); + Notification.requestPermission() } - }, [play]); + }, [play]) useEffect(() => { - setNotifications(tickets); - }, [tickets]); + setNotifications(tickets) + }, [tickets]) useEffect(() => { - - - - ticketIdRef.current = ticketIdUrl; - }, [ticketIdUrl]); + ticketIdRef.current = ticketIdUrl + }, [ticketIdUrl]) 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) { - window.location.reload(true); + window.location.reload(true) - } + } }) socket.on("onlineStatus", (data) => { - if (data.action === "logout") { + if (data.action === "logout") { if (`${user.id}` === data.userOnlineTime['userId']) { socket.emit("online", { logoutUserId: user.id }) - handleLogout(); + handleLogout() } } - - }); + }) // socket.on("isOnline", (data) => { @@ -157,159 +151,145 @@ const NotificationsPopOver = () => { if (user.profile === 'user') { - if (_fifo) { - clearInterval(_fifo); - } + // if (_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) - }, 3000); + }, 3000) + + return () => clearInterval(intID) + } - - - - - return () => { - socket.disconnect(); - }; - }, [user.id, handleLogout, user.profile]); + socket.disconnect() + } + }, [user.id, handleLogout, user.profile]) 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 => { - if (data.action === "updateUnread" || data.action === "delete") { - - + if (data.action === "updateUnread" || data.action === "delete") { setNotifications(prevState => { - const ticketIndex = prevState.findIndex(t => t.id === data.ticketId); + const ticketIndex = prevState.findIndex(t => t.id === data.ticketId) if (ticketIndex !== -1) { - prevState.splice(ticketIndex, 1); - return [...prevState]; + prevState.splice(ticketIndex, 1) + return [...prevState] } - return prevState; - }); + return prevState + }) setDesktopNotifications(prevState => { const notfiticationIndex = prevState.findIndex( n => n.tag === String(data.ticketId) - ); + ) if (notfiticationIndex !== -1) { - prevState[notfiticationIndex].close(); - prevState.splice(notfiticationIndex, 1); - return [...prevState]; + prevState[notfiticationIndex].close() + prevState.splice(notfiticationIndex, 1) + return [...prevState] } - return prevState; - }); + return prevState + }) } - }); - - socket.on("appMessage", data => { - + }) + socket.on("appMessage", data => { if ( data.action === "create" && !data.message.read && (data.ticket.userId === user?.id || !data.ticket.userId) - ) { + ) { + setNotifications(prevState => { - - - setNotifications(prevState => { - - - - // prevState.forEach((e)=>{ - // - // }) - - const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id); + const ticketIndex = prevState.findIndex(t => t.id === data.ticket.id) if (ticketIndex !== -1) { - prevState[ticketIndex] = data.ticket; - return [...prevState]; + prevState[ticketIndex] = data.ticket + return [...prevState] } - return [data.ticket, ...prevState]; - }); - - + return [data.ticket, ...prevState] + }) const shouldNotNotificate = (data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || (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 () => { - socket.disconnect(); - }; - }, [user]); + socket.disconnect() + } + }, [user]) const handleNotifications = data => { - const { message, contact, ticket } = data; + const { message, contact, ticket } = data const options = { body: `${message.body} - ${format(new Date(), "HH:mm")}`, icon: contact.profilePicUrl, tag: ticket.id, renotify: true, - }; + } const notification = new Notification( `${i18n.t("tickets.notification.message")} ${contact.name}`, options - ); + ) notification.onclick = e => { - e.preventDefault(); - window.focus(); - historyRef.current.push(`/tickets/${ticket.id}`); - }; + e.preventDefault() + window.focus() + historyRef.current.push(`/tickets/${ticket.id}`) + } setDesktopNotifications(prevState => { const notfiticationIndex = prevState.findIndex( n => n.tag === notification.tag - ); + ) if (notfiticationIndex !== -1) { - prevState[notfiticationIndex] = notification; - return [...prevState]; + prevState[notfiticationIndex] = notification + return [...prevState] } - return [notification, ...prevState]; - }); + return [notification, ...prevState] + }) - soundAlertRef.current(); - }; + soundAlertRef.current() + } // const handleClick = () => { // setIsOpen(prevState => !prevState); // }; const handleClickAway = () => { - setIsOpen(false); - }; + setIsOpen(false) + } const NotificationTicket = ({ children }) => { - return
{children}
; - }; + return
{children}
+ } return ( <> @@ -357,7 +337,7 @@ const NotificationsPopOver = () => { - ); -}; + ) +} -export default NotificationsPopOver; +export default NotificationsPopOver