feat: added new method to fetch old whatsapp messages

This commit is contained in:
canove
2020-10-30 10:37:57 -03:00
parent 5a51581bd3
commit 099a3354ca
14 changed files with 116 additions and 36 deletions

View File

@@ -2,6 +2,7 @@ import { Request, Response } from "express";
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
import { getIO } from "../libs/socket"; import { getIO } from "../libs/socket";
import Message from "../models/Message";
import ListMessagesService from "../services/MessageServices/ListMessagesService"; import ListMessagesService from "../services/MessageServices/ListMessagesService";
import ShowTicketService from "../services/TicketServices/ShowTicketService"; import ShowTicketService from "../services/TicketServices/ShowTicketService";
@@ -17,6 +18,7 @@ type MessageData = {
body: string; body: string;
fromMe: boolean; fromMe: boolean;
read: boolean; read: boolean;
quotedMsg?: Message;
}; };
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
@@ -35,7 +37,7 @@ 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 }: MessageData = req.body; const { body, quotedMsg }: MessageData = req.body;
const medias = req.files as Express.Multer.File[]; const medias = req.files as Express.Multer.File[];
const ticket = await ShowTicketService(ticketId); const ticket = await ShowTicketService(ticketId);
@@ -47,7 +49,7 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
}) })
); );
} else { } else {
await SendWhatsAppMessage({ body, ticket }); await SendWhatsAppMessage({ body, ticket, quotedMsg });
} }
await SetTicketMessagesAsRead(ticket); await SetTicketMessagesAsRead(ticket);

View File

@@ -13,19 +13,32 @@ export const GetWbotMessage = async (
`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us` `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`
); );
try { let limit = 20;
const chatMessages = await wbotChat.fetchMessages({ limit: 20 });
const msgToDelete = chatMessages.find(msg => msg.id.id === messageId); const fetchWbotMessagesGradually = async (): Promise<void | WbotMessage> => {
const chatMessages = await wbotChat.fetchMessages({ limit });
if (!msgToDelete) { const msgFound = chatMessages.find(msg => msg.id.id === messageId);
throw new Error();
if (!msgFound && limit < 100) {
limit += 20;
return fetchWbotMessagesGradually();
} }
return msgToDelete; return msgFound;
};
try {
const msgFound = await fetchWbotMessagesGradually();
if (!msgFound) {
throw new Error("Cannot found message within 100 last messages");
}
return msgFound;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
throw new AppError("ERR_DELETE_WAPP_MSG"); throw new AppError("ERR_FETCH_WAPP_MSG");
} }
}; };

View File

@@ -0,0 +1,12 @@
import Message from "../models/Message";
import Ticket from "../models/Ticket";
const SerializeWbotMsgId = (ticket: Ticket, message: Message): string => {
const serializedMsgId = `${message.fromMe}_${ticket.contact.number}@${
ticket.isGroup ? "g" : "c"
}.us_${message.id}`;
return serializedMsgId;
};
export default SerializeWbotMsgId;

View File

@@ -28,7 +28,14 @@ const CreateMessageService = async ({
await Message.upsert(messageData); await Message.upsert(messageData);
const message = await Message.findByPk(messageData.id, { const message = await Message.findByPk(messageData.id, {
include: ["contact", "quotedMsg"] include: [
"contact",
{
model: Message,
as: "quotedMsg",
include: ["contact"]
}
]
}); });
if (!message) { if (!message) {

View File

@@ -32,7 +32,14 @@ const ListMessagesService = async ({
const { count, rows: messages } = await Message.findAndCountAll({ const { count, rows: messages } = await Message.findAndCountAll({
where: { ticketId }, where: { ticketId },
limit, limit,
include: ["contact", "quotedMsg"], include: [
"contact",
{
model: Message,
as: "quotedMsg",
include: ["contact"]
}
],
offset, offset,
order: [["createdAt", "DESC"]] order: [["createdAt", "DESC"]]
}); });

View File

@@ -1,23 +1,37 @@
import { Message as WbotMessage } from "whatsapp-web.js"; import { 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";
import GetWbotMessage from "../../helpers/GetWbotMessage";
import SerializeWbotMsgId from "../../helpers/SerializeWbotMsgId";
import Message from "../../models/Message";
import Ticket from "../../models/Ticket"; import Ticket from "../../models/Ticket";
interface Request { interface Request {
body: string; body: string;
ticket: Ticket; ticket: Ticket;
quotedMsg?: Message;
} }
const SendWhatsAppMessage = async ({ const SendWhatsAppMessage = async ({
body, body,
ticket ticket,
quotedMsg
}: Request): Promise<WbotMessage> => { }: Request): Promise<WbotMessage> => {
try { let quotedMsgSerializedId: string | undefined;
const wbot = await GetTicketWbot(ticket); if (quotedMsg) {
await GetWbotMessage(ticket, quotedMsg.id);
quotedMsgSerializedId = SerializeWbotMsgId(ticket, quotedMsg);
}
const wbot = await GetTicketWbot(ticket);
try {
const sentMessage = await wbot.sendMessage( const sentMessage = await wbot.sendMessage(
`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body body,
{
quotedMessageId: quotedMsgSerializedId
}
); );
await ticket.update({ lastMessage: body }); await ticket.update({ lastMessage: body });

View File

@@ -344,7 +344,14 @@ const wbotMessageListener = (wbot: Session): void => {
try { try {
const messageToUpdate = await Message.findByPk(msg.id.id, { const messageToUpdate = await Message.findByPk(msg.id.id, {
include: ["contact"] include: [
"contact",
{
model: Message,
as: "quotedMsg",
include: ["contact"]
}
]
}); });
if (!messageToUpdate) { if (!messageToUpdate) {
return; return;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext, useRef } from "react";
import "emoji-mart/css/emoji-mart.css"; import "emoji-mart/css/emoji-mart.css";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Picker } from "emoji-mart"; import { Picker } from "emoji-mart";
@@ -170,17 +170,24 @@ const MessageInput = ({ ticketStatus }) => {
const [showEmoji, setShowEmoji] = useState(false); const [showEmoji, setShowEmoji] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [recording, setRecording] = useState(false); const [recording, setRecording] = useState(false);
const inputRef = useRef();
const { setReplyingMessage, replyingMessage } = useContext( const { setReplyingMessage, replyingMessage } = useContext(
ReplyMessageContext ReplyMessageContext
); );
useEffect(() => { useEffect(() => {
inputRef.current.focus();
}, [replyingMessage]);
useEffect(() => {
inputRef.current.focus();
return () => { return () => {
setInputMessage(""); setInputMessage("");
setShowEmoji(false); setShowEmoji(false);
setMedias([]); setMedias([]);
setReplyingMessage(null);
}; };
}, [ticketId]); }, [ticketId, setReplyingMessage]);
const handleChangeInput = e => { const handleChangeInput = e => {
setInputMessage(e.target.value); setInputMessage(e.target.value);
@@ -245,6 +252,7 @@ const MessageInput = ({ ticketStatus }) => {
fromMe: true, fromMe: true,
mediaUrl: "", mediaUrl: "",
body: `${username}: ${inputMessage.trim()}`, body: `${username}: ${inputMessage.trim()}`,
quotedMsg: replyingMessage,
}; };
try { try {
await api.post(`/messages/${ticketId}`, message); await api.post(`/messages/${ticketId}`, message);
@@ -264,6 +272,7 @@ const MessageInput = ({ ticketStatus }) => {
setInputMessage(""); setInputMessage("");
setShowEmoji(false); setShowEmoji(false);
setLoading(false); setLoading(false);
setReplyingMessage(null);
}; };
const handleStartRecording = async () => { const handleStartRecording = async () => {
@@ -344,7 +353,7 @@ const MessageInput = ({ ticketStatus }) => {
aria-label="showRecorder" aria-label="showRecorder"
component="span" component="span"
disabled={loading || ticketStatus !== "open"} disabled={loading || ticketStatus !== "open"}
onClick={() => setReplyingMessage({})} onClick={() => setReplyingMessage(null)}
> >
<ClearIcon className={classes.sendMessageIcons} /> <ClearIcon className={classes.sendMessageIcons} />
</IconButton> </IconButton>
@@ -386,7 +395,7 @@ const MessageInput = ({ ticketStatus }) => {
else { else {
return ( return (
<Paper square elevation={0} className={classes.mainWrapper}> <Paper square elevation={0} className={classes.mainWrapper}>
{replyingMessage.id && renderReplyingMessage(replyingMessage)} {replyingMessage && renderReplyingMessage(replyingMessage)}
<div className={classes.newMessageBox}> <div className={classes.newMessageBox}>
<IconButton <IconButton
aria-label="emojiPicker" aria-label="emojiPicker"
@@ -426,7 +435,10 @@ const MessageInput = ({ ticketStatus }) => {
</label> </label>
<div className={classes.messageInputWrapper}> <div className={classes.messageInputWrapper}>
<InputBase <InputBase
inputRef={input => input && input.focus()} inputRef={input => {
input && input.focus();
input && (inputRef.current = input);
}}
className={classes.messageInput} className={classes.messageInput}
placeholder={ placeholder={
ticketStatus === "open" ticketStatus === "open"

View File

@@ -65,9 +65,11 @@ const MessageOptionsMenu = ({ message, menuOpen, handleClose, anchorEl }) => {
open={menuOpen} open={menuOpen}
onClose={handleClose} onClose={handleClose}
> >
<MenuItem onClick={handleOpenConfirmationModal}> {message.fromMe && (
{i18n.t("messageOptionsMenu.delete")} <MenuItem onClick={handleOpenConfirmationModal}>
</MenuItem> {i18n.t("messageOptionsMenu.delete")}
</MenuItem>
)}
<MenuItem onClick={hanldeReplyMessage}> <MenuItem onClick={hanldeReplyMessage}>
{i18n.t("messageOptionsMenu.reply")} {i18n.t("messageOptionsMenu.reply")}
</MenuItem> </MenuItem>

View File

@@ -106,7 +106,7 @@ const useStyles = makeStyles(theme => ({
overflow: "hidden", overflow: "hidden",
}, },
quotedSideLeft: { quotedSideColorLeft: {
flex: "none", flex: "none",
width: "4px", width: "4px",
backgroundColor: "#6bcbef", backgroundColor: "#6bcbef",
@@ -158,7 +158,7 @@ const useStyles = makeStyles(theme => ({
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
}, },
quotedSideRight: { quotedSideColorRight: {
flex: "none", flex: "none",
width: "4px", width: "4px",
backgroundColor: "#35cd96", backgroundColor: "#35cd96",
@@ -170,6 +170,7 @@ const useStyles = makeStyles(theme => ({
color: "#999", color: "#999",
zIndex: 1, zIndex: 1,
backgroundColor: "inherit", backgroundColor: "inherit",
opacity: "90%",
"&:hover, &.Mui-focusVisible": { backgroundColor: "inherit" }, "&:hover, &.Mui-focusVisible": { backgroundColor: "inherit" },
}, },
@@ -534,14 +535,14 @@ const MessagesList = ({ ticketId, isGroup, setReplyingMessage }) => {
})} })}
> >
<span <span
className={clsx(classes.quotedSideLeft, { className={clsx(classes.quotedSideColorLeft, {
[classes.quotedSideRight]: message.quotedMsg?.fromMe, [classes.quotedSideColorRight]: message.quotedMsg?.fromMe,
})} })}
></span> ></span>
<div className={classes.quotedMsg}> <div className={classes.quotedMsg}>
{!message.quotedMsg?.fromMe && ( {!message.quotedMsg?.fromMe && (
<span className={classes.messageContactName}> <span className={classes.messageContactName}>
{message.contact?.name} {message.quotedMsg?.contact?.name}
</span> </span>
)} )}
{message.quotedMsg?.body} {message.quotedMsg?.body}

View File

@@ -3,7 +3,7 @@ import React, { useState, createContext } from "react";
const ReplyMessageContext = createContext(); const ReplyMessageContext = createContext();
const ReplyMessageProvider = ({ children }) => { const ReplyMessageProvider = ({ children }) => {
const [replyingMessage, setReplyingMessage] = useState({}); const [replyingMessage, setReplyingMessage] = useState(null);
return ( return (
<ReplyMessageContext.Provider <ReplyMessageContext.Provider

View File

@@ -335,8 +335,7 @@ const messages = {
ERR_INVALID_CREDENTIALS: "Authentication error. Please try again.", ERR_INVALID_CREDENTIALS: "Authentication error. Please try again.",
ERR_SENDING_WAPP_MSG: ERR_SENDING_WAPP_MSG:
"Error sending WhatsApp message. Check connections page.", "Error sending WhatsApp message. Check connections page.",
ERR_DELETE_WAPP_MSG: ERR_DELETE_WAPP_MSG: "Couldn't delete message from WhatsApp.",
"Couldn't delete message from WhatsApp. Check connections page.",
ERR_OTHER_OPEN_TICKET: ERR_OTHER_OPEN_TICKET:
"There's already an open ticket for this contact.", "There's already an open ticket for this contact.",
ERR_SESSION_EXPIRED: "Session expired. Please login.", ERR_SESSION_EXPIRED: "Session expired. Please login.",
@@ -351,6 +350,8 @@ const messages = {
ERR_NO_WAPP_FOUND: "No WhatsApp found with this ID.", ERR_NO_WAPP_FOUND: "No WhatsApp found with this ID.",
ERR_CREATING_MESSAGE: "Error while creating message on database.", ERR_CREATING_MESSAGE: "Error while creating message on database.",
ERR_CREATING_TICKET: "Error while creating ticket on database.", ERR_CREATING_TICKET: "Error while creating ticket on database.",
ERR_FETCH_WAPP_MSG:
"Error fetching the message in WhtasApp, maybe it is too old.",
}, },
}, },
}, },

View File

@@ -341,8 +341,7 @@ const messages = {
ERR_INVALID_CREDENTIALS: "Error de autenticación. Vuelva a intentarlo.", ERR_INVALID_CREDENTIALS: "Error de autenticación. Vuelva a intentarlo.",
ERR_SENDING_WAPP_MSG: ERR_SENDING_WAPP_MSG:
"Error al enviar el mensaje de WhatsApp. Verifique la página de conexiones.", "Error al enviar el mensaje de WhatsApp. Verifique la página de conexiones.",
ERR_DELETE_WAPP_MSG: ERR_DELETE_WAPP_MSG: "No se pudo borrar el mensaje de WhatsApp.",
"No se pudo borrar el mensaje de WhatsApp. Verifique la página de conexiones.",
ERR_OTHER_OPEN_TICKET: "Ya hay un ticket abierto para este contacto.", ERR_OTHER_OPEN_TICKET: "Ya hay un ticket abierto para este contacto.",
ERR_SESSION_EXPIRED: "Sesión caducada. Inicie sesión.", ERR_SESSION_EXPIRED: "Sesión caducada. Inicie sesión.",
ERR_USER_CREATION_DISABLED: ERR_USER_CREATION_DISABLED:
@@ -357,6 +356,8 @@ const messages = {
ERR_NO_WAPP_FOUND: "No se encontró WhatsApp con este ID.", ERR_NO_WAPP_FOUND: "No se encontró WhatsApp con este ID.",
ERR_CREATING_MESSAGE: "Error al crear el mensaje en la base de datos.", ERR_CREATING_MESSAGE: "Error al crear el mensaje en la base de datos.",
ERR_CREATING_TICKET: "Error al crear el ticket en la base de datos.", ERR_CREATING_TICKET: "Error al crear el ticket en la base de datos.",
ERR_FETCH_WAPP_MSG:
"Error al obtener el mensaje en WhtasApp, tal vez sea demasiado antiguo.",
}, },
}, },
}, },

View File

@@ -340,8 +340,7 @@ const messages = {
"Erro de autenticação. Por favor, tente novamente.", "Erro de autenticação. Por favor, tente novamente.",
ERR_SENDING_WAPP_MSG: ERR_SENDING_WAPP_MSG:
"Erro ao enviar mensagem do WhatsApp. Verifique a página de conexões.", "Erro ao enviar mensagem do WhatsApp. Verifique a página de conexões.",
ERR_DELETE_WAPP_MSG: ERR_DELETE_WAPP_MSG: "Não foi possível excluir a mensagem do WhatsApp.",
"Não foi possível excluir a mensagem do WhatsApp. Verifique a página de conexões.",
ERR_OTHER_OPEN_TICKET: "Já existe um tíquete aberto para este contato.", ERR_OTHER_OPEN_TICKET: "Já existe um tíquete aberto para este contato.",
ERR_SESSION_EXPIRED: "Sessão expirada. Por favor entre.", ERR_SESSION_EXPIRED: "Sessão expirada. Por favor entre.",
ERR_USER_CREATION_DISABLED: ERR_USER_CREATION_DISABLED:
@@ -355,6 +354,8 @@ const messages = {
ERR_NO_WAPP_FOUND: "Nenhum WhatsApp encontrado com este ID.", ERR_NO_WAPP_FOUND: "Nenhum WhatsApp encontrado com este ID.",
ERR_CREATING_MESSAGE: "Erro ao criar mensagem no banco de dados.", ERR_CREATING_MESSAGE: "Erro ao criar mensagem no banco de dados.",
ERR_CREATING_TICKET: "Erro ao criar tíquete no banco de dados.", ERR_CREATING_TICKET: "Erro ao criar tíquete no banco de dados.",
ERR_FETCH_WAPP_MSG:
"Erro ao buscar a mensagem no WhtasApp, talvez ela seja muito antiga.",
}, },
}, },
}, },