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;

View File

@@ -1,8 +1,8 @@
Arguments:
/usr/bin/node /usr/share/yarn/bin/yarn.js sequelize db:migrate
/usr/bin/node /usr/share/yarn/bin/yarn.js add -D @types/qrcode-terminal
PATH:
/home/canove/.vscode-server/bin/a0479759d6e9ea56afa657e454193f72aef85bd0/bin:/home/canove/.zinit/polaris/sbin:/home/canove/.zinit/polaris/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/ProgramData/chocolatey/bin:/mnt/c/Program Files/Microsoft VS Code/bin:/mnt/c/Program Files (x86)/GNU/GnuPG/pub:/mnt/c/Users/cassio/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/cassio/AppData/Local/GitHubDesktop/bin
/home/canove/.vscode-server/bin/58bb7b2331731bf72587010e943852e13e6fd3cf/bin:/home/canove/.zinit/polaris/sbin:/home/canove/.zinit/polaris/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/ProgramData/chocolatey/bin:/mnt/c/Program Files/Microsoft VS Code/bin:/mnt/c/Program Files (x86)/GNU/GnuPG/pub:/mnt/c/Users/cassio/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/cassio/AppData/Local/GitHubDesktop/bin
Yarn version:
1.22.4
@@ -14,12 +14,17 @@ Platform:
linux x64
Trace:
SyntaxError: /home/canove/code_learn/econowhats/backend/package.json: Unexpected token / in JSON at position 222
at JSON.parse (<anonymous>)
at /usr/share/yarn/lib/cli.js:1625:59
at Generator.next (<anonymous>)
at step (/usr/share/yarn/lib/cli.js:310:30)
at /usr/share/yarn/lib/cli.js:321:13
Error: https://registry.yarnpkg.com/@types%2fqrcode-terminal: Not found
at Request.params.callback [as _callback] (/usr/share/yarn/lib/cli.js:66987:18)
at Request.self.callback (/usr/share/yarn/lib/cli.js:140748:22)
at Request.emit (events.js:315:20)
at Request.<anonymous> (/usr/share/yarn/lib/cli.js:141720:10)
at Request.emit (events.js:315:20)
at IncomingMessage.<anonymous> (/usr/share/yarn/lib/cli.js:141642:12)
at Object.onceWrapper (events.js:421:28)
at IncomingMessage.emit (events.js:327:22)
at endReadableNT (_stream_readable.js:1221:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21)
npm manifest:
{
@@ -29,8 +34,8 @@ npm manifest:
"main": "index.js",
"scripts": {
"build": "tsc",
"start": "nodemon dist/server.js",
"dev:server": "ts-node-dev --respawn --transpile-only --ignore node_modules src/server.ts"
// "sequelize": "ts-node-dev ./node_modules/.bin/sequelize"
},
"author": "",
"license": "MIT",
@@ -55,11 +60,14 @@ npm manifest:
"yup": "^0.29.3"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/bluebird": "^3.5.32",
"@types/cors": "^2.8.7",
"@types/express": "^4.17.8",
"@types/jsonwebtoken": "^8.5.0",
"@types/multer": "^1.4.4",
"@types/node": "^14.10.1",
"@types/socket.io": "^2.1.11",
"@types/validator": "^13.1.0",
"@types/yup": "^0.29.7",
"@typescript-eslint/eslint-plugin": "^4.1.0",
@@ -235,6 +243,11 @@ Lockfile:
dependencies:
defer-to-connect "^1.0.1"
"@types/bcryptjs@^2.4.2":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae"
integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==
"@types/bluebird@^3.5.32":
version "3.5.32"
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.32.tgz#381e7b59e39f010d20bbf7e044e48f5caf1ab620"
@@ -267,6 +280,13 @@ Lockfile:
dependencies:
"@types/express" "*"
"@types/engine.io@*":
version "3.1.4"
resolved "https://registry.yarnpkg.com/@types/engine.io/-/engine.io-3.1.4.tgz#3d9472711d179daa7c95c051e50ad411e18a9bdc"
integrity sha512-98rXVukLD6/ozrQ2O80NAlWDGA4INg+tqsEReWJldqyi2fulC9V7Use/n28SWgROXKm6003ycWV4gZHoF8GA6w==
dependencies:
"@types/node" "*"
"@types/express-serve-static-core@*":
version "4.17.12"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz#9a487da757425e4f267e7d1c5720226af7f89591"
@@ -296,6 +316,13 @@ Lockfile:
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/jsonwebtoken@^8.5.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#2531d5e300803aa63279b232c014acf780c981c5"
integrity sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==
dependencies:
"@types/node" "*"
"@types/mime@*":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
@@ -336,6 +363,14 @@ Lockfile:
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/socket.io@^2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.11.tgz#e0d6759880e5f9818d5297a3328b36641bae996b"
integrity sha512-bVprmqPhJMLb9ZCm8g0Xy8kwBFRbnanOWSxzWkDkkIwxTvud5tKMfAJymXX6LQbizUKCS1yima7JM4BeLqjNqA==
dependencies:
"@types/engine.io" "*"
"@types/node" "*"
"@types/strip-bom@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"