feat: handle group messages

This commit is contained in:
canove
2020-09-30 19:16:31 -03:00
parent 3a777dec39
commit 4c67067d8f
15 changed files with 138 additions and 68 deletions

View File

@@ -1,10 +1,8 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Message as WbotMessage } from "whatsapp-web.js";
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
import { getIO } from "../libs/socket"; import { getIO } from "../libs/socket";
import CreateMessageService from "../services/MessageServices/CreateMessageService";
import ListMessagesService from "../services/MessageServices/ListMessagesService"; import ListMessagesService from "../services/MessageServices/ListMessagesService";
import ShowTicketService from "../services/TicketServices/ShowTicketService"; import ShowTicketService from "../services/TicketServices/ShowTicketService";
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage"; import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
@@ -39,44 +37,20 @@ 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 { ticketId } = req.params; const { ticketId } = req.params;
const { body, fromMe, read }: MessageData = req.body; const { body }: MessageData = req.body;
const media = req.file; const media = req.file;
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
let sentMessage: WbotMessage;
if (media) { if (media) {
sentMessage = await SendWhatsAppMedia({ media, ticket }); await SendWhatsAppMedia({ media, ticket });
} else { } 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); await SetTicketMessagesAsRead(ticket);
return res.status(200).json(message); return res.send();
}; };
export const remove = async ( export const remove = async (
@@ -93,5 +67,5 @@ export const remove = async (
message message
}); });
return res.json({ ok: true }); return res.send();
}; };

View File

@@ -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");
}
};

View File

@@ -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");
}
};

View File

@@ -9,7 +9,9 @@ export const GetWbotMessage = async (
): Promise<WbotMessage> => { ): Promise<WbotMessage> => {
const wbot = await GetTicketWbot(ticket); 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 { try {
const chatMessages = await wbotChat.fetchMessages({ limit: 20 }); const chatMessages = await wbotChat.fetchMessages({ limit: 20 });

View File

@@ -37,6 +37,10 @@ class Contact extends Model<Contact> {
@Column @Column
profilePicUrl: string; profilePicUrl: string;
@Default(false)
@Column
isGroup: boolean;
@CreatedAt @CreatedAt
createdAt: Date; createdAt: Date;

View File

@@ -11,7 +11,8 @@ import {
HasMany, HasMany,
AutoIncrement, AutoIncrement,
AfterFind, AfterFind,
BeforeUpdate BeforeUpdate,
Default
} from "sequelize-typescript"; } from "sequelize-typescript";
import Contact from "./Contact"; import Contact from "./Contact";
@@ -35,6 +36,10 @@ class Ticket extends Model<Ticket> {
@Column @Column
lastMessage: string; lastMessage: string;
@Default(false)
@Column
isGroup: boolean;
@CreatedAt @CreatedAt
createdAt: Date; createdAt: Date;

View File

@@ -2,13 +2,13 @@ import Contact from "../../models/Contact";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
const ShowContactService = async (id: string | number): Promise<Contact> => { const ShowContactService = async (id: string | number): Promise<Contact> => {
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); throw new AppError("No contact found with this ID.", 404);
} }
return user; return contact;
}; };
export default ShowContactService; export default ShowContactService;

View File

@@ -2,6 +2,7 @@ import AppError from "../../errors/AppError";
import CheckContactOpenTickets from "../../helpers/CheckContactOpenTickets"; import CheckContactOpenTickets from "../../helpers/CheckContactOpenTickets";
import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
import ShowContactService from "../ContactServices/ShowContactService";
interface Request { interface Request {
contactId: number; contactId: number;
@@ -22,9 +23,12 @@ const CreateTicketService = async ({
await CheckContactOpenTickets(contactId); await CheckContactOpenTickets(contactId);
const { isGroup } = await ShowContactService(contactId);
const { id }: Ticket = await defaultWhatsapp.$create("ticket", { const { id }: Ticket = await defaultWhatsapp.$create("ticket", {
contactId, contactId,
status, status,
isGroup,
userId userId
}); });

View File

@@ -1,3 +1,4 @@
import fs from "fs";
import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js"; import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import GetTicketWbot from "../../helpers/GetTicketWbot"; import GetTicketWbot from "../../helpers/GetTicketWbot";
@@ -18,11 +19,14 @@ const SendWhatsAppMedia = async ({
const newMedia = MessageMedia.fromFilePath(media.path); const newMedia = MessageMedia.fromFilePath(media.path);
const sentMessage = await wbot.sendMessage( const sentMessage = await wbot.sendMessage(
`${ticket.contact.number}@c.us`, `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
newMedia newMedia
); );
await ticket.update({ lastMessage: media.filename }); await ticket.update({ lastMessage: media.filename });
fs.unlinkSync(media.path);
return sentMessage; return sentMessage;
} catch (err) { } catch (err) {
console.log(err); console.log(err);

View File

@@ -16,7 +16,7 @@ const SendWhatsAppMessage = async ({
const wbot = await GetTicketWbot(ticket); const wbot = await GetTicketWbot(ticket);
const sentMessage = await wbot.sendMessage( const sentMessage = await wbot.sendMessage(
`${ticket.contact.number}@c.us`, `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body body
); );

View File

@@ -2,7 +2,7 @@ import path from "path";
import fs from "fs"; import fs from "fs";
import { Op } from "sequelize"; import { Op } from "sequelize";
import { subHours } from "date-fns"; import { subHours } from "date-fns";
import Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { import {
Contact as WbotContact, Contact as WbotContact,
@@ -24,43 +24,88 @@ const verifyContact = async (
profilePicUrl: string profilePicUrl: string
): Promise<Contact> => { ): Promise<Contact> => {
let contact = await Contact.findOne({ let contact = await Contact.findOne({
where: { number: msgContact.number } where: { number: msgContact.id.user }
}); });
if (contact) { if (contact) {
await contact.update({ profilePicUrl }); await contact.update({ profilePicUrl });
} else { } else {
contact = await Contact.create({ contact = await Contact.create({
name: msgContact.pushname || msgContact.number.toString(), name: msgContact.name || msgContact.pushname || msgContact.id.user,
number: msgContact.number, number: msgContact.id.user,
profilePicUrl profilePicUrl
}); });
const io = getIO();
io.emit("contact", {
action: "create",
contact
});
} }
return 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 ( const verifyTicket = async (
contact: Contact, contact: Contact,
whatsappId: number whatsappId: number,
groupContact?: Contact
): Promise<Ticket> => { ): Promise<Ticket> => {
let ticket = await Ticket.findOne({ let ticket = await Ticket.findOne({
where: { where: {
status: { status: {
[Op.or]: ["open", "pending"] [Op.or]: ["open", "pending"]
}, },
contactId: contact.id contactId: groupContact ? groupContact.id : contact.id
}, },
include: ["contact"] 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) { if (!ticket) {
ticket = await Ticket.findOne({ ticket = await Ticket.findOne({
where: { where: {
createdAt: { createdAt: {
[Op.between]: [+subHours(new Date(), 2), +new Date()] [Op.between]: [+subHours(new Date(), 2), +new Date()]
}, },
contactId: contact.id contactId: groupContact ? groupContact.id : contact.id
}, },
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],
include: ["contact"] include: ["contact"]
@@ -73,8 +118,9 @@ const verifyTicket = async (
if (!ticket) { if (!ticket) {
const { id } = await Ticket.create({ const { id } = await Ticket.create({
contactId: contact.id, contactId: groupContact ? groupContact.id : contact.id,
status: "pending", status: "pending",
isGroup: !!groupContact,
whatsappId whatsappId
}); });
@@ -86,7 +132,8 @@ const verifyTicket = async (
const handlMedia = async ( const handlMedia = async (
msg: WbotMessage, msg: WbotMessage,
ticket: Ticket ticket: Ticket,
contact: Contact
): Promise<Message> => { ): Promise<Message> => {
const media = await msg.downloadMedia(); const media = await msg.downloadMedia();
@@ -110,7 +157,9 @@ const handlMedia = async (
const newMessage: Message = await ticket.$create("message", { const newMessage: Message = await ticket.$create("message", {
id: msg.id.id, 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, fromMe: msg.fromMe,
mediaUrl: media.filename, mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0] mediaType: media.mimetype.split("/")[0]
@@ -127,12 +176,13 @@ const handleMessage = async (
let newMessage: Message; let newMessage: Message;
if (msg.hasMedia) { if (msg.hasMedia) {
newMessage = await handlMedia(msg, ticket); newMessage = await handlMedia(msg, ticket, contact);
} else { } else {
newMessage = await ticket.$create("message", { newMessage = await ticket.$create("message", {
id: msg.id.id, id: msg.id.id,
body: msg.body, body: msg.fromMe ? msg.body : `${contact.name}: ${msg.body}`,
fromMe: msg.fromMe fromMe: msg.fromMe,
read: msg.fromMe
}); });
await ticket.update({ lastMessage: msg.body }); await ticket.update({ lastMessage: msg.body });
} }
@@ -150,7 +200,6 @@ const handleMessage = async (
}; };
const isValidMsg = (msg: WbotMessage): boolean => { const isValidMsg = (msg: WbotMessage): boolean => {
if (msg.author) return false;
if ( if (
msg.type === "chat" || msg.type === "chat" ||
msg.type === "audio" || msg.type === "audio" ||
@@ -176,28 +225,22 @@ const wbotMessageListener = (whatsapp: Whatsapp): void => {
try { try {
let msgContact: WbotContact; let msgContact: WbotContact;
let msgGroupContact: WbotContact | null = null;
let groupContact: Contact | undefined;
if (msg.fromMe) { if (msg.fromMe) {
msgContact = await wbot.getContactById(msg.to); msgContact = await wbot.getContactById(msg.to);
} else { } else {
msgContact = await msg.getContact(); msgContact = await msg.getContact();
} }
if (msg.author) {
msgGroupContact = await wbot.getContactById(msg.from);
groupContact = await verifyGroup(msgGroupContact);
}
const profilePicUrl = await msgContact.getProfilePicUrl(); const profilePicUrl = await msgContact.getProfilePicUrl();
const contact = await verifyContact(msgContact, profilePicUrl); const contact = await verifyContact(msgContact, profilePicUrl);
const ticket = await verifyTicket(contact, whatsappId); const ticket = await verifyTicket(contact, whatsappId, groupContact);
// return if message was already created by messageController
if (msg.fromMe) {
const alreadyExists = await Message.findOne({
where: { id: msg.id.id }
});
if (alreadyExists) {
return;
}
}
await handleMessage(msg, ticket, contact); await handleMessage(msg, ticket, contact);
} catch (err) { } catch (err) {

View File

@@ -88,6 +88,7 @@ const NotificationsPopOver = () => {
socket.on("appMessage", data => { socket.on("appMessage", data => {
if ( if (
data.action === "create" && data.action === "create" &&
!data.message.read &&
(data.ticket.userId === userId || !data.ticket.userId) (data.ticket.userId === userId || !data.ticket.userId)
) { ) {
setNotifications(prevState => { setNotifications(prevState => {
@@ -103,7 +104,8 @@ const NotificationsPopOver = () => {
(ticketIdRef.current && (ticketIdRef.current &&
data.message.ticketId === ticketIdRef.current && data.message.ticketId === ticketIdRef.current &&
document.visibilityState === "visible") || document.visibilityState === "visible") ||
(data.ticket.userId && data.ticket.userId !== userId) (data.ticket.userId && data.ticket.userId !== userId) ||
data.ticket.isGroup
) )
return; return;
else { else {

View File

@@ -103,7 +103,7 @@ const TicketActionButtons = ({ ticket }) => {
color="primary" color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", userId)} onClick={e => handleUpdateTicketStatus(e, "open", userId)}
> >
ACCEPT {i18n.t("messagesList.header.buttons.accept")}
</Button> </Button>
)} )}
</div> </div>

View File

@@ -243,7 +243,8 @@ const messages = {
buttons: { buttons: {
return: "Return", return: "Return",
resolve: "Resolve", resolve: "Resolve",
reopen: "Reopen", reopen: "Reopen",
accept: "Accept"
}, },
}, },
}, },

View File

@@ -245,6 +245,7 @@ const messages = {
return: "Retornar", return: "Retornar",
resolve: "Resolver", resolve: "Resolver",
reopen: "Reabrir", reopen: "Reabrir",
accept: "Aceitar",
}, },
}, },
}, },