mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-17 19:37:02 +00:00
feat: handle group messages
This commit is contained in:
@@ -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<Response> => {
|
||||
|
||||
export const store = async (req: Request, res: Response): Promise<Response> => {
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
@@ -9,7 +9,9 @@ export const GetWbotMessage = async (
|
||||
): Promise<WbotMessage> => {
|
||||
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 });
|
||||
|
||||
@@ -37,6 +37,10 @@ class Contact extends Model<Contact> {
|
||||
@Column
|
||||
profilePicUrl: string;
|
||||
|
||||
@Default(false)
|
||||
@Column
|
||||
isGroup: boolean;
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
@@ -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<Ticket> {
|
||||
@Column
|
||||
lastMessage: string;
|
||||
|
||||
@Default(false)
|
||||
@Column
|
||||
isGroup: boolean;
|
||||
|
||||
@CreatedAt
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import Contact from "../../models/Contact";
|
||||
import AppError from "../../errors/AppError";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return user;
|
||||
return contact;
|
||||
};
|
||||
|
||||
export default ShowContactService;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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<Contact> => {
|
||||
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<Ticket> => {
|
||||
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<Message> => {
|
||||
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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -103,7 +103,7 @@ const TicketActionButtons = ({ ticket }) => {
|
||||
color="primary"
|
||||
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
|
||||
>
|
||||
ACCEPT
|
||||
{i18n.t("messagesList.header.buttons.accept")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -243,7 +243,8 @@ const messages = {
|
||||
buttons: {
|
||||
return: "Return",
|
||||
resolve: "Resolve",
|
||||
reopen: "Reopen",
|
||||
reopen: "Reopen",
|
||||
accept: "Accept"
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -245,6 +245,7 @@ const messages = {
|
||||
return: "Retornar",
|
||||
resolve: "Resolver",
|
||||
reopen: "Reabrir",
|
||||
accept: "Aceitar",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user