migrated wbot initialization to typescript

This commit is contained in:
canove
2020-09-20 14:06:20 -03:00
parent 8b99690c4a
commit cb3ba09252
10 changed files with 383 additions and 345 deletions

5
backend/src/@types/WAWebJS.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare namespace WAWebJS {
export interface ClientInfo {
meuId: number;
}
}

View File

@@ -1,5 +1,5 @@
declare namespace Express {
export interface Request {
user: { id: string; profile: string };
}
export interface Request {
user: { id: string; profile: string };
}
}

View File

@@ -0,0 +1 @@
declare module "qrcode-terminal";

View File

@@ -1,110 +0,0 @@
const qrCode = require("qrcode-terminal");
const { Client } = require("whatsapp-web.js");
const Whatsapp = require("../models/Whatsapp");
const { getIO } = require("../libs/socket");
let sessions = [];
module.exports = {
initWbot: async whatsapp => {
try {
const io = getIO();
const sessionName = whatsapp.name;
let sessionCfg;
if (whatsapp && whatsapp.session) {
sessionCfg = JSON.parse(whatsapp.session);
}
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
if (sessionIndex !== -1) {
sessions[sessionIndex].destroy();
sessions.splice(sessionIndex, 1);
}
const wbot = new Client({
session: sessionCfg,
restartOnAuthFail: true
});
wbot.initialize();
wbot.on("qr", async qr => {
console.log("Session:", sessionName);
qrCode.generate(qr, { small: true });
await whatsapp.update({ qrcode: qr, status: "qrcode" });
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
});
wbot.on("authenticated", async session => {
console.log("Session:", sessionName, "AUTHENTICATED");
await whatsapp.update({
session: JSON.stringify(session),
status: "authenticated"
});
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
});
wbot.on("auth_failure", async msg => {
console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg);
await whatsapp.update({ session: "" });
});
wbot.on("ready", async () => {
console.log("Session:", sessionName, "READY");
await whatsapp.update({
status: "CONNECTED",
qrcode: ""
});
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
wbot.sendPresenceAvailable();
});
wbot.id = whatsapp.id;
sessions.push(wbot);
} catch (err) {
console.log(err);
}
return null;
},
getWbot: whatsappId => {
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
if (sessionIndex === -1) {
console.log("This Wbot session is not initialized");
return null;
}
return sessions[sessionIndex];
},
removeWbot: whatsappId => {
try {
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
if (sessionIndex !== -1) {
sessions[sessionIndex].destroy();
sessions.splice(sessionIndex, 1);
}
} catch (err) {
console.log(err);
}
}
};

107
backend/src/libs/wbot.ts Normal file
View File

@@ -0,0 +1,107 @@
import qrCode from "qrcode-terminal";
import { Client } from "whatsapp-web.js";
import { getIO } from "./socket";
import Whatsapp from "../models/Whatsapp";
const sessions: Client[] = [];
export const initWbot = async (whatsapp: Whatsapp): Promise<void> => {
console.log("starting");
try {
const io = getIO();
const sessionName = whatsapp.name;
let sessionCfg;
if (whatsapp && whatsapp.session) {
sessionCfg = JSON.parse(whatsapp.session);
}
const sessionIndex = sessions.findIndex(s => s.id === whatsapp.id);
if (sessionIndex !== -1) {
sessions[sessionIndex].destroy();
sessions.splice(sessionIndex, 1);
}
const wbot = new Client({
session: sessionCfg,
restartOnAuthFail: true
});
wbot.initialize();
wbot.on("qr", async qr => {
console.log("Session:", sessionName);
qrCode.generate(qr, { small: true });
await whatsapp.update({ qrcode: qr, status: "qrcode" });
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
});
wbot.on("authenticated", async session => {
console.log("Session:", sessionName, "AUTHENTICATED");
await whatsapp.update({
session: JSON.stringify(session),
status: "authenticated"
});
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
});
wbot.on("auth_failure", async msg => {
console.error("Session:", sessionName, "AUTHENTICATION FAILURE", msg);
await whatsapp.update({ session: "" });
});
wbot.on("ready", async () => {
console.log("Session:", sessionName, "READY");
await whatsapp.update({
status: "CONNECTED",
qrcode: ""
});
io.emit("whatsappSession", {
action: "update",
session: whatsapp
});
wbot.sendPresenceAvailable();
});
wbot.id = whatsapp.id;
sessions.push(wbot);
} catch (err) {
console.log(err);
}
};
export const getWbot = (whatsappId: number): Client | null => {
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
if (sessionIndex === -1) {
console.log("This Wbot session is not initialized");
return null;
}
return sessions[sessionIndex];
};
export const removeWbot = (whatsappId: number): void => {
try {
const sessionIndex = sessions.findIndex(s => s.id === whatsappId);
if (sessionIndex !== -1) {
sessions[sessionIndex].destroy();
sessions.splice(sessionIndex, 1);
}
} catch (err) {
console.log(err);
}
};

View File

@@ -11,13 +11,13 @@ import AppError from "./errors/AppError";
import routes from "./routes";
import { initIO } from "./libs/socket";
import "./database";
import { initWbot } from "./libs/wbot";
// import path from "path";
// const { initWbot } = require("./libs/wbot");
// const wbotMessageListener = require("./services/wbotMessageListener");
// const wbotMonitor = require("./services/wbotMonitor");
// const Whatsapp = require("./models/Whatsapp");
import Whatsapp from "./models/Whatsapp";
Sentry.init({ dsn: process.env.SENTRY_DSN });
@@ -62,21 +62,21 @@ io.on("connection", socket => {
});
});
// const startWhatsAppSessions = async () => {
// const whatsapps = await Whatsapp.findAll();
// if (whatsapps.length > 0) {
// whatsapps.forEach(whatsapp => {
// initWbot(whatsapp)
// .then(() => {
// wbotMessageListener(whatsapp);
// wbotMonitor(whatsapp);
// })
// .catch(err => console.log(err));
// });
// }
// };
// startWhatsAppSessions();
const startWhatsAppSessions = async () => {
const whatsapps = await Whatsapp.findAll();
if (whatsapps.length > 0) {
whatsapps.forEach(whatsapp => {
initWbot(whatsapp)
.then(() => {
console.log("initialized!!");
// wbotMessageListener(whatsapp);
// wbotMonitor(whatsapp);
})
.catch(err => console.log(err));
});
}
};
startWhatsAppSessions();
// app.use(Sentry.Handlers.errorHandler());

View File

@@ -0,0 +1,206 @@
const path = require("path");
const fs = require("fs");
const { Op } = require("sequelize");
const { subHours } = require("date-fns");
const Sentry = require("@sentry/node");
const Contact = require("../models/Contact");
const Ticket = require("../models/Ticket");
const Message = require("../models/Message");
const Whatsapp = require("../models/Whatsapp");
const { getIO } = require("../libs/socket");
const { getWbot, initWbot } = require("../libs/wbot");
const verifyContact = async (msgContact, profilePicUrl) => {
let contact = await Contact.findOne({
where: { number: msgContact.number }
});
if (contact) {
await contact.update({ profilePicUrl: profilePicUrl });
} else {
contact = await Contact.create({
name: msgContact.pushname || msgContact.number.toString(),
number: msgContact.number,
profilePicUrl: profilePicUrl
});
}
return contact;
};
const verifyTicket = async (contact, whatsappId) => {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending"]
},
contactId: contact.id
}
});
if (!ticket) {
ticket = await Ticket.findOne({
where: {
createdAt: { [Op.between]: [subHours(new Date(), 2), new Date()] },
contactId: contact.id
},
order: [["createdAt", "DESC"]]
});
if (ticket) {
await ticket.update({ status: "pending", userId: null });
}
}
if (!ticket) {
ticket = await Ticket.create({
contactId: contact.id,
status: "pending",
whatsappId
});
}
return ticket;
};
const handlMedia = async (msg, ticket) => {
const media = await msg.downloadMedia();
let newMessage;
console.log("criando midia");
if (media) {
if (!media.filename) {
let ext = media.mimetype.split("/")[1].split(";")[0];
media.filename = `${new Date().getTime()}.${ext}`;
}
fs.writeFile(
path.join(__dirname, "..", "..", "public", media.filename),
media.data,
"base64",
err => {
console.log(err);
}
);
newMessage = await ticket.createMessage({
id: msg.id.id,
body: msg.body || media.filename,
fromMe: msg.fromMe,
mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0]
});
await ticket.update({ lastMessage: msg.body || media.filename });
}
return newMessage;
};
const handleMessage = async (msg, ticket, contact) => {
const io = getIO();
let newMessage;
if (msg.hasMedia) {
newMessage = await handlMedia(msg, ticket);
} else {
newMessage = await ticket.createMessage({
id: msg.id.id,
body: msg.body,
fromMe: msg.fromMe
});
await ticket.update({ lastMessage: msg.body });
}
const serializedMessage = {
...newMessage.dataValues,
mediaUrl: `${
newMessage.mediaUrl
? `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${newMessage.mediaUrl}`
: ""
}`
};
const serializaedTicket = {
...ticket.dataValues,
contact: contact
};
io.to(ticket.id).to("notification").emit("appMessage", {
action: "create",
message: serializedMessage,
ticket: serializaedTicket,
contact: contact
});
};
const wbotMessageListener = whatsapp => {
const whatsappId = whatsapp.id;
const wbot = getWbot(whatsappId);
const io = getIO();
wbot.on("message_create", async msg => {
// console.log(msg);
if (
msg.from === "status@broadcast" ||
msg.type === "location" ||
msg.type === "call_log" ||
msg.author != null // Ignore Group Messages
) {
return;
}
try {
let msgContact;
if (msg.fromMe) {
msgContact = await wbot.getContactById(msg.to);
} else {
msgContact = await msg.getContact();
}
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;
}
}
await handleMessage(msg, ticket, contact);
} catch (err) {
Sentry.captureException(err);
console.log(err);
}
});
wbot.on("message_ack", async (msg, ack) => {
try {
const messageToUpdate = await Message.findOne({
where: { id: msg.id.id }
});
if (!messageToUpdate) {
return;
}
await messageToUpdate.update({ ack: ack });
io.to(messageToUpdate.ticketId).emit("appMessage", {
action: "update",
message: messageToUpdate
});
} catch (err) {
Sentry.captureException(err);
console.log(err);
}
});
};
module.exports = wbotMessageListener;

View File

@@ -1,206 +0,0 @@
const path = require("path");
const fs = require("fs");
const { Op } = require("sequelize");
const { subHours } = require("date-fns");
const Sentry = require("@sentry/node");
const Contact = require("../models/Contact");
const Ticket = require("../models/Ticket");
const Message = require("../models/Message");
const Whatsapp = require("../models/Whatsapp");
const { getIO } = require("../libs/socket");
const { getWbot, initWbot } = require("../libs/wbot");
const verifyContact = async (msgContact, profilePicUrl) => {
let contact = await Contact.findOne({
where: { number: msgContact.number },
});
if (contact) {
await contact.update({ profilePicUrl: profilePicUrl });
} else {
contact = await Contact.create({
name: msgContact.pushname || msgContact.number.toString(),
number: msgContact.number,
profilePicUrl: profilePicUrl,
});
}
return contact;
};
const verifyTicket = async (contact, whatsappId) => {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending"],
},
contactId: contact.id,
},
});
if (!ticket) {
ticket = await Ticket.findOne({
where: {
createdAt: { [Op.between]: [subHours(new Date(), 2), new Date()] },
contactId: contact.id,
},
order: [["createdAt", "DESC"]],
});
if (ticket) {
await ticket.update({ status: "pending", userId: null });
}
}
if (!ticket) {
ticket = await Ticket.create({
contactId: contact.id,
status: "pending",
whatsappId,
});
}
return ticket;
};
const handlMedia = async (msg, ticket) => {
const media = await msg.downloadMedia();
let newMessage;
console.log("criando midia");
if (media) {
if (!media.filename) {
let ext = media.mimetype.split("/")[1].split(";")[0];
media.filename = `${new Date().getTime()}.${ext}`;
}
fs.writeFile(
path.join(__dirname, "..", "..", "public", media.filename),
media.data,
"base64",
err => {
console.log(err);
}
);
newMessage = await ticket.createMessage({
id: msg.id.id,
body: msg.body || media.filename,
fromMe: msg.fromMe,
mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0],
});
await ticket.update({ lastMessage: msg.body || media.filename });
}
return newMessage;
};
const handleMessage = async (msg, ticket, contact) => {
const io = getIO();
let newMessage;
if (msg.hasMedia) {
newMessage = await handlMedia(msg, ticket);
} else {
newMessage = await ticket.createMessage({
id: msg.id.id,
body: msg.body,
fromMe: msg.fromMe,
});
await ticket.update({ lastMessage: msg.body });
}
const serializedMessage = {
...newMessage.dataValues,
mediaUrl: `${
newMessage.mediaUrl
? `${process.env.BACKEND_URL}:${process.env.PROXY_PORT}/public/${newMessage.mediaUrl}`
: ""
}`,
};
const serializaedTicket = {
...ticket.dataValues,
contact: contact,
};
io.to(ticket.id).to("notification").emit("appMessage", {
action: "create",
message: serializedMessage,
ticket: serializaedTicket,
contact: contact,
});
};
const wbotMessageListener = whatsapp => {
const whatsappId = whatsapp.id;
const wbot = getWbot(whatsappId);
const io = getIO();
wbot.on("message_create", async msg => {
// console.log(msg);
if (
msg.from === "status@broadcast" ||
msg.type === "location" ||
msg.type === "call_log" ||
msg.author != null // Ignore Group Messages
) {
return;
}
try {
let msgContact;
if (msg.fromMe) {
msgContact = await wbot.getContactById(msg.to);
} else {
msgContact = await msg.getContact();
}
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;
}
}
await handleMessage(msg, ticket, contact);
} catch (err) {
Sentry.captureException(err);
console.log(err);
}
});
wbot.on("message_ack", async (msg, ack) => {
try {
const messageToUpdate = await Message.findOne({
where: { id: msg.id.id },
});
if (!messageToUpdate) {
return;
}
await messageToUpdate.update({ ack: ack });
io.to(messageToUpdate.ticketId).emit("appMessage", {
action: "update",
message: messageToUpdate,
});
} catch (err) {
Sentry.captureException(err);
console.log(err);
}
});
};
module.exports = wbotMessageListener;