mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-18 03:39:29 +00:00
feat: start adding auto reply
This commit is contained in:
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,6 +19,9 @@ module.exports = {
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
greetingMessage: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
32
backend/src/services/QueueService/AssociateWhatsappQueue.ts
Normal file
32
backend/src/services/QueueService/AssociateWhatsappQueue.ts
Normal 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;
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user