improvement: code cleanup

This commit is contained in:
canove
2021-01-07 22:01:13 -03:00
parent 896f122cf7
commit c8c8dc43e9
9 changed files with 297 additions and 250 deletions

View File

@@ -0,0 +1,13 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Tickets", "unreadMessages", {
type: DataTypes.INTEGER
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Tickets", "unreadMessages");
}
};

View File

@@ -15,6 +15,8 @@ const SetTicketMessagesAsRead = async (ticket: Ticket): Promise<void> => {
}
);
await ticket.update({ unreadMessages: 0 });
try {
const wbot = await GetTicketWbot(ticket);
wbot.sendSeen(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`);

View File

@@ -4,7 +4,7 @@ import { getIO } from "./socket";
import Whatsapp from "../models/Whatsapp";
import AppError from "../errors/AppError";
import { logger } from "../utils/logger";
// import { handleMessage } from "../services/WbotServices/wbotMessageListener";
import { handleMessage } from "../services/WbotServices/wbotMessageListener";
interface Session extends Client {
id?: number;
@@ -12,20 +12,25 @@ interface Session extends Client {
const sessions: Session[] = [];
// const syncUnreadMessages = async (wbot: Session) => {
// const chats = await wbot.getChats();
const syncUnreadMessages = async (wbot: Session) => {
const chats = await wbot.getChats();
// chats.forEach(async chat => {
// if (chat.unreadCount > 0) {
// const unreadMessages = await chat.fetchMessages({
// limit: chat.unreadCount
// });
// unreadMessages.forEach(msg => {
// handleMessage(msg, wbot);
// });
// }
// });
// };
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */
for (const chat of chats) {
if (chat.unreadCount > 0) {
const unreadMessages = await chat.fetchMessages({
limit: chat.unreadCount
});
for (const msg of unreadMessages) {
await handleMessage(msg, wbot);
}
await chat.sendSeen();
}
}
};
export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
return new Promise((resolve, reject) => {
@@ -76,14 +81,16 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
});
wbot.on("authenticated", async session => {
logger.info("Session:", sessionName, "AUTHENTICATED");
logger.info(`Session: ${sessionName} AUTHENTICATED`);
await whatsapp.update({
session: JSON.stringify(session)
});
});
wbot.on("auth_failure", async msg => {
console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg);
console.error(
`Session: ${sessionName} AUTHENTICATION FAILURE! Reason: ${msg}`
);
if (whatsapp.retries > 1) {
await whatsapp.update({ session: "", retries: 0 });
@@ -106,8 +113,6 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
wbot.on("ready", async () => {
logger.info("Session:", sessionName, "READY");
// syncUnreadMessages(wbot);
await whatsapp.update({
status: "CONNECTED",
qrcode: "",
@@ -119,14 +124,15 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
session: whatsapp
});
wbot.sendPresenceAvailable();
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
if (sessionIndex === -1) {
wbot.id = whatsapp.id;
sessions.push(wbot);
}
wbot.sendPresenceAvailable();
syncUnreadMessages(wbot);
resolve(wbot);
});
} catch (err) {

View File

@@ -4,14 +4,11 @@ import {
CreatedAt,
UpdatedAt,
Model,
DataType,
PrimaryKey,
ForeignKey,
BelongsTo,
HasMany,
AutoIncrement,
AfterFind,
BeforeUpdate,
Default
} from "sequelize-typescript";
@@ -30,7 +27,7 @@ class Ticket extends Model<Ticket> {
@Column({ defaultValue: "pending" })
status: string;
@Column(DataType.VIRTUAL)
@Column
unreadMessages: number;
@Column
@@ -69,26 +66,6 @@ class Ticket extends Model<Ticket> {
@HasMany(() => Message)
messages: Message[];
@AfterFind
static async countTicketsUnreadMessages(tickets: Ticket[]): Promise<void> {
if (tickets && tickets.length > 0) {
await Promise.all(
tickets.map(async ticket => {
ticket.unreadMessages = await Message.count({
where: { ticketId: ticket.id, read: false }
});
})
);
}
}
@BeforeUpdate
static async countTicketUnreadMessags(ticket: Ticket): Promise<void> {
ticket.unreadMessages = await Message.count({
where: { ticketId: ticket.id, read: false }
});
}
}
export default Ticket;

View File

@@ -0,0 +1,59 @@
import { getIO } from "../../libs/socket";
import Contact from "../../models/Contact";
interface ExtraInfo {
name: string;
value: string;
}
interface Request {
name: string;
number: string;
isGroup: boolean;
email?: string;
profilePicUrl?: string;
extraInfo?: ExtraInfo[];
}
const CreateOrUpdateContactService = async ({
name,
number: rawNumber,
profilePicUrl,
isGroup,
email = "",
extraInfo = []
}: Request): Promise<Contact> => {
const number = isGroup ? rawNumber : rawNumber.replace(/[^0-9]/g, "");
const io = getIO();
let contact: Contact | null;
contact = await Contact.findOne({ where: { number } });
if (contact) {
contact.update({ profilePicUrl });
io.emit("contact", {
action: "update",
contact
});
} else {
contact = await Contact.create({
name,
number,
profilePicUrl,
email,
isGroup,
extraInfo
});
io.emit("contact", {
action: "create",
contact
});
}
return contact;
};
export default CreateOrUpdateContactService;

View File

@@ -1,6 +1,6 @@
import AppError from "../../errors/AppError";
import { getIO } from "../../libs/socket";
import Message from "../../models/Message";
import ShowTicketService from "../TicketServices/ShowTicketService";
import Ticket from "../../models/Ticket";
interface MessageData {
id: string;
@@ -19,17 +19,16 @@ interface Request {
const CreateMessageService = async ({
messageData
}: Request): Promise<Message> => {
const ticket = await ShowTicketService(messageData.ticketId);
if (!ticket) {
throw new AppError("ERR_NO_TICKET_FOUND", 404);
}
await Message.upsert(messageData);
const message = await Message.findByPk(messageData.id, {
include: [
"contact",
{
model: Ticket,
as: "ticket",
include: ["contact"]
},
{
model: Message,
as: "quotedMsg",
@@ -39,9 +38,20 @@ const CreateMessageService = async ({
});
if (!message) {
throw new AppError("ERR_CREATING_MESSAGE", 501);
throw new Error("ERR_CREATING_MESSAGE");
}
const io = getIO();
io.to(message.ticketId.toString())
.to(message.ticket.status)
.to("notification")
.emit("appMessage", {
action: "create",
message,
ticket: message.ticket,
contact: message.ticket.contact
});
return message;
};

View File

@@ -0,0 +1,81 @@
import { subHours } from "date-fns";
import { Op } from "sequelize";
import Contact from "../../models/Contact";
import Ticket from "../../models/Ticket";
import ShowTicketService from "./ShowTicketService";
const FindOrCreateTicketService = async (
contact: Contact,
whatsappId: number,
unreadMessages: number,
groupContact?: Contact
): Promise<Ticket> => {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending"]
},
contactId: groupContact ? groupContact.id : contact.id
},
include: ["contact"]
});
if (ticket) {
ticket.update({ unreadMessages });
}
if (!ticket && groupContact) {
ticket = await Ticket.findOne({
where: {
contactId: groupContact.id
},
order: [["updatedAt", "DESC"]],
include: ["contact"]
});
if (ticket) {
await ticket.update({
status: "pending",
userId: null,
unreadMessages
});
}
}
if (!ticket && !groupContact) {
ticket = await Ticket.findOne({
where: {
updatedAt: {
[Op.between]: [+subHours(new Date(), 2), +new Date()]
},
contactId: contact.id
},
order: [["updatedAt", "DESC"]],
include: ["contact"]
});
if (ticket) {
await ticket.update({
status: "pending",
userId: null,
unreadMessages
});
}
}
if (!ticket) {
const { id } = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: "pending",
isGroup: !!groupContact,
unreadMessages,
whatsappId
});
ticket = await ShowTicketService(id);
}
return ticket;
};
export default FindOrCreateTicketService;

View File

@@ -1,8 +1,6 @@
import { join } from "path";
import { promisify } from "util";
import { writeFile } from "fs";
import { Op } from "sequelize";
import { subHours } from "date-fns";
import * as Sentry from "@sentry/node";
import {
@@ -17,10 +15,10 @@ import Ticket from "../../models/Ticket";
import Message from "../../models/Message";
import { getIO } from "../../libs/socket";
import AppError from "../../errors/AppError";
import ShowTicketService from "../TicketServices/ShowTicketService";
import CreateMessageService from "../MessageServices/CreateMessageService";
import { logger } from "../../utils/logger";
import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService";
import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService";
interface Session extends Client {
id?: number;
@@ -28,141 +26,48 @@ interface Session extends Client {
const writeFileAsync = promisify(writeFile);
const verifyContact = async (
msgContact: WbotContact,
profilePicUrl: string
): Promise<Contact> => {
const io = getIO();
const verifyContact = async (msgContact: WbotContact): Promise<Contact> => {
const profilePicUrl = await msgContact.getProfilePicUrl();
let contact = await Contact.findOne({
where: { number: msgContact.id.user }
});
const contactData = {
name: msgContact.name || msgContact.pushname || msgContact.id.user,
number: msgContact.id.user,
profilePicUrl,
isGroup: msgContact.isGroup
};
if (contact) {
await contact.update({ profilePicUrl });
io.emit("contact", {
action: "update",
contact
});
} else {
contact = await Contact.create({
name: msgContact.name || msgContact.pushname || msgContact.id.user,
number: msgContact.id.user,
profilePicUrl
});
io.emit("contact", {
action: "create",
contact
});
}
const contact = CreateOrUpdateContactService(contactData);
return contact;
};
const verifyGroup = async (msgGroupContact: WbotContact) => {
const profilePicUrl = await msgGroupContact.getProfilePicUrl();
const verifyQuotedMessage = async (
msg: WbotMessage
): Promise<Message | null> => {
if (!msg.hasQuotedMsg) return null;
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
});
}
const wbotQuotedMsg = await msg.getQuotedMessage();
return groupContact;
};
const verifyTicket = async (
contact: Contact,
whatsappId: number,
groupContact?: Contact
): Promise<Ticket> => {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending"]
},
contactId: groupContact ? groupContact.id : contact.id
},
include: ["contact"]
const quotedMsg = await Message.findOne({
where: { id: wbotQuotedMsg.id.id }
});
if (!ticket && groupContact) {
ticket = await Ticket.findOne({
where: {
contactId: groupContact.id
},
order: [["createdAt", "DESC"]],
include: ["contact"]
});
if (!quotedMsg) return null;
if (ticket) {
await ticket.update({ status: "pending", userId: null });
}
}
if (!ticket) {
ticket = await Ticket.findOne({
where: {
updatedAt: {
[Op.between]: [+subHours(new Date(), 2), +new Date()]
},
contactId: groupContact ? groupContact.id : contact.id
},
order: [["updatedAt", "DESC"]],
include: ["contact"]
});
if (ticket) {
await ticket.update({ status: "pending", userId: null });
}
}
if (!ticket) {
const { id } = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: "pending",
isGroup: !!groupContact,
whatsappId
});
ticket = await ShowTicketService(id);
}
return ticket;
return quotedMsg;
};
const verifyMedia = async (
const verifyMediaMessage = async (
msg: WbotMessage,
ticket: Ticket,
contact: Contact
): Promise<Message> => {
let quotedMsg: Message | null = null;
const quotedMsg = await verifyQuotedMessage(msg);
const media = await msg.downloadMedia();
if (!media) {
throw new AppError("ERR_WAPP_DOWNLOAD_MEDIA");
}
if (msg.hasQuotedMsg) {
const wbotQuotedMsg = await msg.getQuotedMessage();
quotedMsg = await Message.findByPk(wbotQuotedMsg.id.id);
throw new Error("ERR_WAPP_DOWNLOAD_MEDIA");
}
if (!media.filename) {
@@ -194,8 +99,8 @@ const verifyMedia = async (
};
const newMessage = await CreateMessageService({ messageData });
await ticket.update({ lastMessage: msg.body || media.filename });
return newMessage;
};
@@ -204,43 +109,21 @@ const verifyMessage = async (
ticket: Ticket,
contact: Contact
) => {
let newMessage: Message | null;
let quotedMsg: Message | null = null;
const quotedMsg = await verifyQuotedMessage(msg);
if (msg.hasQuotedMsg) {
const wbotQuotedMsg = await msg.getQuotedMessage();
const messageData = {
id: msg.id.id,
ticketId: ticket.id,
contactId: msg.fromMe ? undefined : contact.id,
body: msg.body,
fromMe: msg.fromMe,
mediaType: msg.type,
read: msg.fromMe,
quotedMsgId: quotedMsg?.id
};
quotedMsg = await Message.findByPk(wbotQuotedMsg.id.id);
}
if (msg.hasMedia) {
newMessage = await verifyMedia(msg, ticket, contact);
} else {
const messageData = {
id: msg.id.id,
ticketId: ticket.id,
contactId: msg.fromMe ? undefined : contact.id,
body: msg.body,
fromMe: msg.fromMe,
mediaType: msg.type,
read: msg.fromMe,
quotedMsgId: quotedMsg?.id
};
newMessage = await CreateMessageService({ messageData });
await ticket.update({ lastMessage: msg.body });
}
const io = getIO();
io.to(ticket.id.toString())
.to(ticket.status)
.to("notification")
.emit("appMessage", {
action: "create",
message: newMessage,
ticket,
contact
});
await CreateMessageService({ messageData });
await ticket.update({ lastMessage: msg.body });
};
const isValidMsg = (msg: WbotMessage): boolean => {
@@ -263,50 +146,66 @@ const handleMessage = async (
msg: WbotMessage,
wbot: Session
): Promise<void> => {
if (!isValidMsg(msg)) {
return;
}
try {
let msgContact: WbotContact;
let groupContact: Contact | undefined;
if (msg.fromMe) {
msgContact = await wbot.getContactById(msg.to);
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
// the media itself comes on body of message, as base64
// if this is the case, return and let this media be handled by media_uploaded event
// it should be improoved to handle the base64 media here in future versions
if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return;
} else {
msgContact = await msg.getContact();
}
const chat = await msg.getChat();
if (chat.isGroup) {
let msgGroupContact;
if (msg.fromMe) {
msgGroupContact = await wbot.getContactById(msg.to);
} else {
msgGroupContact = await wbot.getContactById(msg.from);
return new Promise<void>((resolve, reject) => {
(async () => {
if (!isValidMsg(msg)) {
return;
}
groupContact = await verifyGroup(msgGroupContact);
}
try {
let msgContact: WbotContact;
let groupContact: Contact | undefined;
const profilePicUrl = await msgContact.getProfilePicUrl();
const contact = await verifyContact(msgContact, profilePicUrl);
const ticket = await verifyTicket(contact, wbot.id!, groupContact);
if (msg.fromMe) {
// media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc"
// in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true"
await verifyMessage(msg, ticket, contact);
} catch (err) {
Sentry.captureException(err);
logger.error(err);
}
if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard")
return;
msgContact = await wbot.getContactById(msg.to);
} else {
msgContact = await msg.getContact();
}
const chat = await msg.getChat();
if (chat.isGroup) {
let msgGroupContact;
if (msg.fromMe) {
msgGroupContact = await wbot.getContactById(msg.to);
} else {
msgGroupContact = await wbot.getContactById(msg.from);
}
groupContact = await verifyContact(msgGroupContact);
}
const contact = await verifyContact(msgContact);
const ticket = await FindOrCreateTicketService(
contact,
wbot.id!,
chat.unreadCount,
groupContact
);
if (msg.hasMedia) {
await verifyMediaMessage(msg, ticket, contact);
resolve();
} else {
await verifyMessage(msg, ticket, contact);
resolve();
}
await verifyMessage(msg, ticket, contact);
} catch (err) {
Sentry.captureException(err);
logger.error(`Error handling whatsapp message: Err: ${err}`);
reject(err);
}
})();
});
};
const handleMsgAck = async (msg: WbotMessage, ack: MessageAck) => {
@@ -336,7 +235,7 @@ const handleMsgAck = async (msg: WbotMessage, ack: MessageAck) => {
});
} catch (err) {
Sentry.captureException(err);
logger.log(err);
logger.error(`Error handling message ack. Err: ${err}`);
}
};

View File

@@ -19,7 +19,7 @@ const wbotMonitor = async (
try {
wbot.on("change_state", async newState => {
logger.info("Monitor session:", sessionName, newState);
logger.info(`Monitor session: ${sessionName}, ${newState}`);
try {
await whatsapp.update({ status: newState });
} catch (err) {
@@ -53,7 +53,7 @@ const wbotMonitor = async (
});
wbot.on("disconnected", async reason => {
logger.info("Disconnected session:", sessionName, reason);
logger.info(`Disconnected session: ${sessionName}, reason: ${reason}`);
try {
await whatsapp.update({ status: "OPENING", session: "" });
} catch (err) {