diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index f902260..7aa0bc6 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -1,10 +1,8 @@ import { Request, Response } from "express"; -import { Message as WbotMessage } from "whatsapp-web.js"; import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; import { getIO } from "../libs/socket"; -import CreateMessageService from "../services/MessageServices/CreateMessageService"; import ListMessagesService from "../services/MessageServices/ListMessagesService"; import ShowTicketService from "../services/TicketServices/ShowTicketService"; import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage"; @@ -39,44 +37,20 @@ export const index = async (req: Request, res: Response): Promise => { export const store = async (req: Request, res: Response): Promise => { const { ticketId } = req.params; - const { body, fromMe, read }: MessageData = req.body; + const { body }: MessageData = req.body; const media = req.file; const ticket = await ShowTicketService(ticketId); - let sentMessage: WbotMessage; - if (media) { - sentMessage = await SendWhatsAppMedia({ media, ticket }); + await SendWhatsAppMedia({ media, ticket }); } else { - sentMessage = await SendWhatsAppMessage({ body, ticket }); + await SendWhatsAppMessage({ body, ticket }); } - const newMessage = { - id: sentMessage.id.id, - body, - fromMe, - read, - mediaType: sentMessage.type, - mediaUrl: media?.filename - }; - - const message = await CreateMessageService({ - messageData: newMessage, - ticketId: ticket.id - }); - - const io = getIO(); - io.to(ticketId).to("notification").to(ticket.status).emit("appMessage", { - action: "create", - message, - ticket, - contact: ticket.contact - }); - await SetTicketMessagesAsRead(ticket); - return res.status(200).json(message); + return res.send(); }; export const remove = async ( @@ -93,5 +67,5 @@ export const remove = async ( message }); - return res.json({ ok: true }); + return res.send(); }; diff --git a/backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts b/backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts new file mode 100644 index 0000000..3e7ba47 --- /dev/null +++ b/backend/src/database/migrations/20200930162323-add-isGroup-column-to-tickets.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Tickets", "isGroup", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Tickets", "isGroup"); + } +}; diff --git a/backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts b/backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts new file mode 100644 index 0000000..d2037ec --- /dev/null +++ b/backend/src/database/migrations/20200930194808-add-isGroup-column-to-contacts.ts @@ -0,0 +1,15 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Contacts", "isGroup", { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Contacts", "isGroup"); + } +}; diff --git a/backend/src/helpers/GetWbotMessage.ts b/backend/src/helpers/GetWbotMessage.ts index 918f42e..0caaafe 100644 --- a/backend/src/helpers/GetWbotMessage.ts +++ b/backend/src/helpers/GetWbotMessage.ts @@ -9,7 +9,9 @@ export const GetWbotMessage = async ( ): Promise => { const wbot = await GetTicketWbot(ticket); - const wbotChat = await wbot.getChatById(`${ticket.contact.number}@c.us`); + const wbotChat = await wbot.getChatById( + `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` + ); try { const chatMessages = await wbotChat.fetchMessages({ limit: 20 }); diff --git a/backend/src/models/Contact.ts b/backend/src/models/Contact.ts index 44302f5..d7c4c93 100644 --- a/backend/src/models/Contact.ts +++ b/backend/src/models/Contact.ts @@ -37,6 +37,10 @@ class Contact extends Model { @Column profilePicUrl: string; + @Default(false) + @Column + isGroup: boolean; + @CreatedAt createdAt: Date; diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index 094df8f..9a07a0a 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -11,7 +11,8 @@ import { HasMany, AutoIncrement, AfterFind, - BeforeUpdate + BeforeUpdate, + Default } from "sequelize-typescript"; import Contact from "./Contact"; @@ -35,6 +36,10 @@ class Ticket extends Model { @Column lastMessage: string; + @Default(false) + @Column + isGroup: boolean; + @CreatedAt createdAt: Date; diff --git a/backend/src/services/ContactServices/ShowContactService.ts b/backend/src/services/ContactServices/ShowContactService.ts index a301cc2..bbe4b87 100644 --- a/backend/src/services/ContactServices/ShowContactService.ts +++ b/backend/src/services/ContactServices/ShowContactService.ts @@ -2,13 +2,13 @@ import Contact from "../../models/Contact"; import AppError from "../../errors/AppError"; const ShowContactService = async (id: string | number): Promise => { - const user = await Contact.findByPk(id, { include: ["extraInfo"] }); + const contact = await Contact.findByPk(id, { include: ["extraInfo"] }); - if (!user) { + if (!contact) { throw new AppError("No contact found with this ID.", 404); } - return user; + return contact; }; export default ShowContactService; diff --git a/backend/src/services/TicketServices/CreateTicketService.ts b/backend/src/services/TicketServices/CreateTicketService.ts index 5abffdd..93477ab 100644 --- a/backend/src/services/TicketServices/CreateTicketService.ts +++ b/backend/src/services/TicketServices/CreateTicketService.ts @@ -2,6 +2,7 @@ import AppError from "../../errors/AppError"; import CheckContactOpenTickets from "../../helpers/CheckContactOpenTickets"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import Ticket from "../../models/Ticket"; +import ShowContactService from "../ContactServices/ShowContactService"; interface Request { contactId: number; @@ -22,9 +23,12 @@ const CreateTicketService = async ({ await CheckContactOpenTickets(contactId); + const { isGroup } = await ShowContactService(contactId); + const { id }: Ticket = await defaultWhatsapp.$create("ticket", { contactId, status, + isGroup, userId }); diff --git a/backend/src/services/WbotServices/SendWhatsAppMedia.ts b/backend/src/services/WbotServices/SendWhatsAppMedia.ts index 8afade7..2d9e16e 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMedia.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMedia.ts @@ -1,3 +1,4 @@ +import fs from "fs"; import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js"; import AppError from "../../errors/AppError"; import GetTicketWbot from "../../helpers/GetTicketWbot"; @@ -18,11 +19,14 @@ const SendWhatsAppMedia = async ({ const newMedia = MessageMedia.fromFilePath(media.path); const sentMessage = await wbot.sendMessage( - `${ticket.contact.number}@c.us`, + `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, newMedia ); await ticket.update({ lastMessage: media.filename }); + + fs.unlinkSync(media.path); + return sentMessage; } catch (err) { console.log(err); diff --git a/backend/src/services/WbotServices/SendWhatsAppMessage.ts b/backend/src/services/WbotServices/SendWhatsAppMessage.ts index 049b1f0..dfa340b 100644 --- a/backend/src/services/WbotServices/SendWhatsAppMessage.ts +++ b/backend/src/services/WbotServices/SendWhatsAppMessage.ts @@ -16,7 +16,7 @@ const SendWhatsAppMessage = async ({ const wbot = await GetTicketWbot(ticket); const sentMessage = await wbot.sendMessage( - `${ticket.contact.number}@c.us`, + `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, body ); diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index 6c7d218..ecf1e9f 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -2,7 +2,7 @@ import path from "path"; import fs from "fs"; import { Op } from "sequelize"; import { subHours } from "date-fns"; -import Sentry from "@sentry/node"; +import * as Sentry from "@sentry/node"; import { Contact as WbotContact, @@ -24,43 +24,88 @@ const verifyContact = async ( profilePicUrl: string ): Promise => { let contact = await Contact.findOne({ - where: { number: msgContact.number } + where: { number: msgContact.id.user } }); if (contact) { await contact.update({ profilePicUrl }); } else { contact = await Contact.create({ - name: msgContact.pushname || msgContact.number.toString(), - number: msgContact.number, + name: msgContact.name || msgContact.pushname || msgContact.id.user, + number: msgContact.id.user, profilePicUrl }); + const io = getIO(); + io.emit("contact", { + action: "create", + contact + }); } return contact; }; +const verifyGroup = async (msgGroupContact: WbotContact) => { + const profilePicUrl = await msgGroupContact.getProfilePicUrl(); + + let groupContact = await Contact.findOne({ + where: { number: msgGroupContact.id.user } + }); + if (groupContact) { + await groupContact.update({ profilePicUrl }); + } else { + groupContact = await Contact.create({ + name: msgGroupContact.name, + number: msgGroupContact.id.user, + isGroup: msgGroupContact.isGroup, + profilePicUrl + }); + const io = getIO(); + io.emit("contact", { + action: "create", + contact: groupContact + }); + } + + return groupContact; +}; + const verifyTicket = async ( contact: Contact, - whatsappId: number + whatsappId: number, + groupContact?: Contact ): Promise => { let ticket = await Ticket.findOne({ where: { status: { [Op.or]: ["open", "pending"] }, - contactId: contact.id + contactId: groupContact ? groupContact.id : contact.id }, include: ["contact"] }); + if (!ticket && groupContact) { + ticket = await Ticket.findOne({ + where: { + contactId: groupContact.id + }, + order: [["createdAt", "DESC"]], + include: ["contact"] + }); + + if (ticket) { + await ticket.update({ status: "pending", userId: null }); + } + } + if (!ticket) { ticket = await Ticket.findOne({ where: { createdAt: { [Op.between]: [+subHours(new Date(), 2), +new Date()] }, - contactId: contact.id + contactId: groupContact ? groupContact.id : contact.id }, order: [["createdAt", "DESC"]], include: ["contact"] @@ -73,8 +118,9 @@ const verifyTicket = async ( if (!ticket) { const { id } = await Ticket.create({ - contactId: contact.id, + contactId: groupContact ? groupContact.id : contact.id, status: "pending", + isGroup: !!groupContact, whatsappId }); @@ -86,7 +132,8 @@ const verifyTicket = async ( const handlMedia = async ( msg: WbotMessage, - ticket: Ticket + ticket: Ticket, + contact: Contact ): Promise => { const media = await msg.downloadMedia(); @@ -110,7 +157,9 @@ const handlMedia = async ( const newMessage: Message = await ticket.$create("message", { id: msg.id.id, - body: msg.body || media.filename, + body: msg.fromMe + ? msg.body + : `${contact.name}: ${msg.body ? msg.body : media.filename}`, fromMe: msg.fromMe, mediaUrl: media.filename, mediaType: media.mimetype.split("/")[0] @@ -127,12 +176,13 @@ const handleMessage = async ( let newMessage: Message; if (msg.hasMedia) { - newMessage = await handlMedia(msg, ticket); + newMessage = await handlMedia(msg, ticket, contact); } else { newMessage = await ticket.$create("message", { id: msg.id.id, - body: msg.body, - fromMe: msg.fromMe + body: msg.fromMe ? msg.body : `${contact.name}: ${msg.body}`, + fromMe: msg.fromMe, + read: msg.fromMe }); await ticket.update({ lastMessage: msg.body }); } @@ -150,7 +200,6 @@ const handleMessage = async ( }; const isValidMsg = (msg: WbotMessage): boolean => { - if (msg.author) return false; if ( msg.type === "chat" || msg.type === "audio" || @@ -176,28 +225,22 @@ const wbotMessageListener = (whatsapp: Whatsapp): void => { try { let msgContact: WbotContact; + let msgGroupContact: WbotContact | null = null; + let groupContact: Contact | undefined; if (msg.fromMe) { msgContact = await wbot.getContactById(msg.to); } else { msgContact = await msg.getContact(); } + if (msg.author) { + msgGroupContact = await wbot.getContactById(msg.from); + groupContact = await verifyGroup(msgGroupContact); + } const profilePicUrl = await msgContact.getProfilePicUrl(); const contact = await verifyContact(msgContact, profilePicUrl); - const ticket = await verifyTicket(contact, whatsappId); - - // return if message was already created by messageController - - if (msg.fromMe) { - const alreadyExists = await Message.findOne({ - where: { id: msg.id.id } - }); - - if (alreadyExists) { - return; - } - } + const ticket = await verifyTicket(contact, whatsappId, groupContact); await handleMessage(msg, ticket, contact); } catch (err) { diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index a08e39f..06d24eb 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -88,6 +88,7 @@ const NotificationsPopOver = () => { socket.on("appMessage", data => { if ( data.action === "create" && + !data.message.read && (data.ticket.userId === userId || !data.ticket.userId) ) { setNotifications(prevState => { @@ -103,7 +104,8 @@ const NotificationsPopOver = () => { (ticketIdRef.current && data.message.ticketId === ticketIdRef.current && document.visibilityState === "visible") || - (data.ticket.userId && data.ticket.userId !== userId) + (data.ticket.userId && data.ticket.userId !== userId) || + data.ticket.isGroup ) return; else { diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index 2a0c774..e4ccca6 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -103,7 +103,7 @@ const TicketActionButtons = ({ ticket }) => { color="primary" onClick={e => handleUpdateTicketStatus(e, "open", userId)} > - ACCEPT + {i18n.t("messagesList.header.buttons.accept")} )} diff --git a/frontend/src/translate/languages/en.js b/frontend/src/translate/languages/en.js index e9704e3..d7442e1 100644 --- a/frontend/src/translate/languages/en.js +++ b/frontend/src/translate/languages/en.js @@ -243,7 +243,8 @@ const messages = { buttons: { return: "Return", resolve: "Resolve", - reopen: "Reopen", + reopen: "Reopen", + accept: "Accept" }, }, }, diff --git a/frontend/src/translate/languages/pt.js b/frontend/src/translate/languages/pt.js index da30faf..2b2da33 100644 --- a/frontend/src/translate/languages/pt.js +++ b/frontend/src/translate/languages/pt.js @@ -245,6 +245,7 @@ const messages = { return: "Retornar", resolve: "Resolver", reopen: "Reabrir", + accept: "Aceitar", }, }, },