feat: start adding auto reply

This commit is contained in:
canove
2021-01-09 18:02:54 -03:00
parent 3c6d0660d1
commit cca253cd0a
17 changed files with 272 additions and 94 deletions

View File

@@ -11,9 +11,9 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
};
export const store = async (req: Request, res: Response): Promise<Response> => {
const { name, color } = req.body;
const { name, color, greetingMessage } = req.body;
const queue = await Queue.create({ name, color });
const queue = await Queue.create({ name, color, greetingMessage });
return res.status(200).json(queue);
};

View File

@@ -9,9 +9,14 @@ import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsServi
import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService";
import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService";
interface QueueData {
id: number;
optionNumber: number;
}
interface WhatsappData {
name: string;
queueIds: number[];
queuesData: QueueData[];
greetingMessage?: string;
status?: string;
isDefault?: boolean;
}
@@ -23,13 +28,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
};
export const store = async (req: Request, res: Response): Promise<Response> => {
const { name, status, isDefault, queueIds }: WhatsappData = req.body;
const {
name,
status,
isDefault,
greetingMessage,
queuesData
}: WhatsappData = req.body;
const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({
name,
status,
isDefault,
queueIds
greetingMessage,
queuesData
});
// StartWhatsAppSession(whatsapp);

View File

@@ -19,6 +19,9 @@ module.exports = {
allowNull: false,
unique: true
},
greetingMessage: {
type: DataTypes.TEXT
},
createdAt: {
type: DataTypes.DATE,
allowNull: false

View File

@@ -3,6 +3,9 @@ import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.createTable("WhatsappQueues", {
optionNumber: {
type: DataTypes.INTEGER
},
whatsappId: {
type: DataTypes.INTEGER,
primaryKey: true

View File

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

View File

@@ -8,7 +8,8 @@ import {
AutoIncrement,
AllowNull,
Unique,
BelongsToMany
BelongsToMany,
HasMany
} from "sequelize-typescript";
import User from "./User";
import UserQueue from "./UserQueue";
@@ -33,6 +34,9 @@ class Queue extends Model<Queue> {
@Column
color: string;
@Column
greetingMessage: string;
@CreatedAt
createdAt: Date;

View File

@@ -47,6 +47,9 @@ class Whatsapp extends Model<Whatsapp> {
@Column
retries: number;
@Column(DataType.TEXT)
greetingMessage: string;
@Default(false)
@AllowNull
@Column
@@ -63,6 +66,9 @@ class Whatsapp extends Model<Whatsapp> {
@BelongsToMany(() => Queue, () => WhatsappQueue)
queues: Array<Queue & { WhatsappQueue: WhatsappQueue }>;
@HasMany(() => WhatsappQueue)
whatsappQueues: WhatsappQueue[];
}
export default Whatsapp;

View File

@@ -4,13 +4,17 @@ import {
CreatedAt,
UpdatedAt,
Model,
ForeignKey
ForeignKey,
BelongsTo
} from "sequelize-typescript";
import Queue from "./Queue";
import Whatsapp from "./Whatsapp";
@Table
class WhatsappQueue extends Model<WhatsappQueue> {
@Column
optionNumber: number;
@ForeignKey(() => Whatsapp)
@Column
whatsappId: number;
@@ -24,6 +28,9 @@ class WhatsappQueue extends Model<WhatsappQueue> {
@UpdatedAt
updatedAt: Date;
@BelongsTo(() => Queue)
queue: Queue;
}
export default WhatsappQueue;

View File

@@ -0,0 +1,32 @@
import Whatsapp from "../../models/Whatsapp";
import WhatsappQueue from "../../models/WhatsappQueue";
interface QueueData {
id: number;
optionNumber: number;
}
const AssociateWhatsappQueue = async (
whatsapp: Whatsapp,
queuesData: QueueData[]
): Promise<void> => {
const queueIds = queuesData.map(({ id }) => id);
await whatsapp.$set("queues", queueIds);
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */
for (const queueData of queuesData) {
await WhatsappQueue.update(
{ optionNumber: queueData.optionNumber },
{
where: {
whatsappId: whatsapp.id,
queueId: queueData.id
}
}
);
}
};
export default AssociateWhatsappQueue;

View File

@@ -16,23 +16,19 @@ const FindOrCreateTicketService = async (
[Op.or]: ["open", "pending"]
},
contactId: groupContact ? groupContact.id : contact.id
},
include: ["contact"]
}
});
if (ticket) {
await ticket.update({ unreadMessages });
return ticket;
}
if (groupContact) {
if (!ticket && groupContact) {
ticket = await Ticket.findOne({
where: {
contactId: groupContact.id
},
order: [["updatedAt", "DESC"]],
include: ["contact"]
order: [["updatedAt", "DESC"]]
});
if (ticket) {
@@ -41,10 +37,10 @@ const FindOrCreateTicketService = async (
userId: null,
unreadMessages
});
return ticket;
}
} else {
}
if (!ticket && !groupContact) {
ticket = await Ticket.findOne({
where: {
updatedAt: {
@@ -52,8 +48,7 @@ const FindOrCreateTicketService = async (
},
contactId: contact.id
},
order: [["updatedAt", "DESC"]],
include: ["contact"]
order: [["updatedAt", "DESC"]]
});
if (ticket) {
@@ -62,20 +57,20 @@ const FindOrCreateTicketService = async (
userId: null,
unreadMessages
});
return ticket;
}
}
const { id } = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: "pending",
isGroup: !!groupContact,
unreadMessages,
whatsappId
});
if (!ticket) {
ticket = await Ticket.create({
contactId: groupContact ? groupContact.id : contact.id,
status: "pending",
isGroup: !!groupContact,
unreadMessages,
whatsappId
});
}
ticket = await ShowTicketService(id);
ticket = await ShowTicketService(ticket.id);
return ticket;
};

View File

@@ -2,6 +2,7 @@ import Ticket from "../../models/Ticket";
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
import User from "../../models/User";
import Queue from "../../models/Queue";
const ShowTicketService = async (id: string | number): Promise<Ticket> => {
const ticket = await Ticket.findByPk(id, {
@@ -16,6 +17,11 @@ const ShowTicketService = async (id: string | number): Promise<Ticket> => {
model: User,
as: "user",
attributes: ["id", "name"]
},
{
model: Queue,
as: "queue",
attributes: ["id", "name", "color"]
}
]
});

View File

@@ -1,8 +1,8 @@
import Whatsapp from "../../models/Whatsapp";
import ListWhatsAppsService from "../WhatsappService/ListWhatsAppsService";
import { StartWhatsAppSession } from "./StartWhatsAppSession";
export const StartAllWhatsAppsSessions = async (): Promise<void> => {
const whatsapps = await Whatsapp.findAll();
const whatsapps = await ListWhatsAppsService();
if (whatsapps.length > 0) {
whatsapps.forEach(whatsapp => {
StartWhatsAppSession(whatsapp);

View File

@@ -19,6 +19,7 @@ import CreateMessageService from "../MessageServices/CreateMessageService";
import { logger } from "../../utils/logger";
import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService";
import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService";
import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService";
interface Session extends Client {
id?: number;
@@ -126,6 +127,54 @@ const verifyMessage = async (
await CreateMessageService({ messageData });
};
const verifyQueue = async (
wbot: Session,
msg: WbotMessage,
ticket: Ticket,
contact: Contact
) => {
const { whatsappQueues, greetingMessage } = await ShowWhatsAppService(
wbot.id!
);
if (whatsappQueues.length === 1) {
await ticket.$set("queue", whatsappQueues[0].queue);
// TODO sendTicketQueueUpdate to frontend
return;
}
const selectedOption = msg.body[0];
const validOption = whatsappQueues.find(
q => q.optionNumber === +selectedOption
);
if (validOption) {
await ticket.$set("queue", validOption.queue);
const body = `\u200e ${validOption.queue.greetingMessage}`;
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
await verifyMessage(sentMessage, ticket, contact);
// TODO sendTicketQueueUpdate to frontend
} else {
let options = "";
whatsappQueues.forEach(whatsQueue => {
options += `*${whatsQueue.optionNumber}* - ${whatsQueue.queue.name}\n`;
});
const body = `\u200e ${greetingMessage}\n\n${options}`;
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
await verifyMessage(sentMessage, ticket, contact);
}
};
const isValidMsg = (msg: WbotMessage): boolean => {
if (msg.from === "status@broadcast") return false;
if (
@@ -146,64 +195,64 @@ const handleMessage = async (
msg: WbotMessage,
wbot: Session
): Promise<void> => {
return new Promise<void>((resolve, reject) => {
(async () => {
if (!isValidMsg(msg)) {
return;
if (!isValidMsg(msg)) {
return;
}
try {
let msgContact: WbotContact;
let groupContact: Contact | undefined;
if (msg.fromMe) {
// messages sent automatically by wbot have a special character in front of it
// if so, this message was already been stored in database;
if (/\u200e/.test(msg.body[0])) return;
// 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"
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);
}
try {
let msgContact: WbotContact;
let groupContact: Contact | undefined;
groupContact = await verifyContact(msgGroupContact);
}
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"
const contact = await verifyContact(msgContact);
const ticket = await FindOrCreateTicketService(
contact,
wbot.id!,
chat.unreadCount,
groupContact
);
if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard")
return;
if (msg.hasMedia) {
await verifyMediaMessage(msg, ticket, contact);
} else {
await verifyMessage(msg, ticket, contact);
}
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();
}
} catch (err) {
Sentry.captureException(err);
logger.error(`Error handling whatsapp message: Err: ${err}`);
reject(err);
}
})();
});
if (!ticket.queue && !chat.isGroup && !msg.fromMe) {
await verifyQueue(wbot, msg, ticket, contact);
}
} catch (err) {
Sentry.captureException(err);
logger.error(`Error handling whatsapp message: Err: ${err}`);
}
};
const handleMsgAck = async (msg: WbotMessage, ack: MessageAck) => {

View File

@@ -2,10 +2,16 @@ import * as Yup from "yup";
import AppError from "../../errors/AppError";
import Whatsapp from "../../models/Whatsapp";
import AssociateWhatsappQueue from "../QueueService/AssociateWhatsappQueue";
interface QueueData {
id: number;
optionNumber: number;
}
interface Request {
name: string;
queueIds: number[];
queuesData: QueueData[];
greetingMessage?: string;
status?: string;
isDefault?: boolean;
}
@@ -18,7 +24,8 @@ interface Response {
const CreateWhatsAppService = async ({
name,
status = "OPENING",
queueIds = [],
queuesData = [],
greetingMessage,
isDefault = false
}: Request): Promise<Response> => {
const schema = Yup.object().shape({
@@ -68,12 +75,13 @@ const CreateWhatsAppService = async ({
{
name,
status,
greetingMessage,
isDefault
},
{ include: ["queues"] }
);
await whatsapp.$set("queues", queueIds);
await AssociateWhatsappQueue(whatsapp, queuesData);
await whatsapp.reload();

View File

@@ -1,11 +1,24 @@
import Queue from "../../models/Queue";
import Whatsapp from "../../models/Whatsapp";
import WhatsappQueue from "../../models/WhatsappQueue";
const ListWhatsAppsService = async (): Promise<Whatsapp[]> => {
const whatsapps = await Whatsapp.findAll({
include: [
{ model: Queue, as: "queues", attributes: ["id", "name", "color"] }
]
{
model: WhatsappQueue,
as: "whatsappQueues",
attributes: ["optionNumber"],
include: [
{
model: Queue,
as: "queue",
attributes: ["id", "name", "color", "greetingMessage"]
}
]
}
],
order: [["whatsappQueues", "optionNumber", "ASC"]]
});
return whatsapps;

View File

@@ -1,12 +1,25 @@
import Whatsapp from "../../models/Whatsapp";
import AppError from "../../errors/AppError";
import Queue from "../../models/Queue";
import WhatsappQueue from "../../models/WhatsappQueue";
const ShowWhatsAppService = async (id: string | number): Promise<Whatsapp> => {
const whatsapp = await Whatsapp.findByPk(id, {
include: [
{ model: Queue, as: "queues", attributes: ["id", "name", "color"] }
]
{
model: WhatsappQueue,
as: "whatsappQueues",
attributes: ["optionNumber"],
include: [
{
model: Queue,
as: "queue",
attributes: ["id", "name", "color", "greetingMessage"]
}
]
}
],
order: [["whatsappQueues", "optionNumber", "ASC"]]
});
if (!whatsapp) {

View File

@@ -4,13 +4,19 @@ import { Op } from "sequelize";
import AppError from "../../errors/AppError";
import Whatsapp from "../../models/Whatsapp";
import ShowWhatsAppService from "./ShowWhatsAppService";
import AssociateWhatsappQueue from "../QueueService/AssociateWhatsappQueue";
interface QueueData {
id: number;
optionNumber: number;
}
interface WhatsappData {
name?: string;
status?: string;
session?: string;
isDefault?: boolean;
queueIds?: number[];
greetingMessage?: string;
queuesData?: QueueData[];
}
interface Request {
@@ -32,7 +38,14 @@ const UpdateWhatsAppService = async ({
isDefault: Yup.boolean()
});
const { name, status, isDefault, session, queueIds = [] } = whatsappData;
const {
name,
status,
isDefault,
session,
greetingMessage,
queuesData = []
} = whatsappData;
try {
await schema.validate({ name, status, isDefault });
@@ -57,10 +70,11 @@ const UpdateWhatsAppService = async ({
name,
status,
session,
greetingMessage,
isDefault
});
await whatsapp.$set("queues", queueIds);
await AssociateWhatsappQueue(whatsapp, queuesData);
await whatsapp.reload();