Compare commits

...

2 Commits

Author SHA1 Message Date
adriano 5366f9943b chore: removed test code 2024-03-13 11:25:00 -03:00
adriano d47a36d8b2 feat: Allow identifying messages sent by the agent and the IVR 2024-03-13 11:19:40 -03:00
14 changed files with 186 additions and 96 deletions

View File

@ -21,6 +21,7 @@ import sendWhatsAppMessageOfficialAPI from "../helpers/sendWhatsAppMessageOffici
import Whatsapp from "../models/Whatsapp"; import Whatsapp from "../models/Whatsapp";
import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs"; import checkLastClientMsg24hs from "../helpers/CheckLastClientMsg24hs";
import AppError from "../errors/AppError"; import AppError from "../errors/AppError";
import { get } from "../helpers/RedisClient";
type IndexQuery = { type IndexQuery = {
pageNumber: string; pageNumber: string;
@ -97,7 +98,8 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
} }
const name = params.find((p: any) => p?.template_name); const name = params.find((p: any) => p?.template_name);
const { language }: any = params?.find((p: any) => p?.language) || 'pt_BR' const { language }: any =
params?.find((p: any) => p?.language) || "pt_BR";
const { template_name } = name; const { template_name } = name;

View File

@ -5,11 +5,9 @@ import DeleteQueueService from "../services/QueueService/DeleteQueueService";
import ListQueuesService from "../services/QueueService/ListQueuesService"; import ListQueuesService from "../services/QueueService/ListQueuesService";
import ShowQueueService from "../services/QueueService/ShowQueueService"; import ShowQueueService from "../services/QueueService/ShowQueueService";
import UpdateQueueService from "../services/QueueService/UpdateQueueService"; import UpdateQueueService from "../services/QueueService/UpdateQueueService";
import Queue from "../models/Queue" import Queue from "../models/Queue";
import AppError from "../errors/AppError" import AppError from "../errors/AppError";
import { get, set } from "../helpers/RedisClient"; import { del, get, set } from "../helpers/RedisClient";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const queues = await ListQueuesService(); const queues = await ListQueuesService();
@ -125,7 +123,7 @@ export const customization = async (
await set("ura", ura); await set("ura", ura);
const _ura = await get("ura"); const _ura = await get({ key: "ura", parse: true });
console.log("_URA: ", _ura); console.log("_URA: ", _ura);
return res.status(200).json({ new_queues }); return res.status(200).json({ new_queues });
@ -164,6 +162,8 @@ export const remove = async (
await DeleteQueueService(queueId); await DeleteQueueService(queueId);
await del(`queue:${queueId}`);
const io = getIO(); const io = getIO();
io.emit("queue", { io.emit("queue", {
action: "delete", action: "delete",

View File

@ -75,6 +75,7 @@ import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import CreateContactService from "../services/ContactServices/CreateContactService"; import CreateContactService from "../services/ContactServices/CreateContactService";
import { botSendMessage } from "../services/WbotServices/wbotMessageListener"; import { botSendMessage } from "../services/WbotServices/wbotMessageListener";
import WhatsappQueue from "../models/WhatsappQueue"; import WhatsappQueue from "../models/WhatsappQueue";
import { get } from "../helpers/RedisClient"
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
const { const {

View File

@ -230,7 +230,10 @@ export const weebhook = async (
req.body.entry[0].changes[0].value.metadata.display_phone_number; req.body.entry[0].changes[0].value.metadata.display_phone_number;
let type = message.type; let type = message.type;
const contact_to_exist = await get("whatsapp:*", `${contact_to}`); const contact_to_exist = await get({
key: "whatsapp:*",
value: `${contact_to}`
});
if (contact_to_exist == null) { if (contact_to_exist == null) {
console.log( console.log(
@ -408,7 +411,15 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
}); });
} else { } else {
await set(`whatsapp:${whatsapp.id}`, `${number}`); await set(
`whatsapp:${whatsapp.id}`,
JSON.stringify({
number: whatsapp?.number,
id: whatsapp?.id,
greetingMessage: whatsapp?.greetingMessage,
phoneNumberId: whatsapp?.phoneNumberId
})
);
} }
const io = getIO(); const io = getIO();
@ -484,7 +495,15 @@ export const update = async (
client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}` client_url: `${process.env.BACKEND_URL_RAW}:${process.env.PORT}`
}); });
} else { } else {
await set(`whatsapp:${whatsapp.id}`, `${number}`); await set(
`whatsapp:${whatsapp.id}`,
JSON.stringify({
number: whatsapp?.number,
id: whatsapp?.id,
greetingMessage: whatsapp?.greetingMessage,
phoneNumberId: whatsapp?.phoneNumberId
})
);
} }
const io = getIO(); const io = getIO();

View File

@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Messages", "fromAgent", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Messages", "fromAgent");
}
};

View File

@ -5,8 +5,7 @@ import ListUsersService from "../services/UserServices/ListUsersService";
import { get } from "./RedisClient"; import { get } from "./RedisClient";
const _botIsOnQueue = async (botName: string) => { const _botIsOnQueue = async (botName: string) => {
const botInfo = await get({ key: "botInfo", parse: true });
const botInfo = await get("botInfo");
if ( if (
botInfo && botInfo &&

View File

@ -8,6 +8,12 @@ type WhatsappData = {
value?: string; value?: string;
}; };
type getData = {
key: string;
value?: string;
parse?: boolean;
};
export async function set(key: string, value: string | object) { export async function set(key: string, value: string | object) {
if (typeof value == "object") await redis.set(key, JSON.stringify(value)); if (typeof value == "object") await redis.set(key, JSON.stringify(value));
else { else {
@ -15,21 +21,30 @@ export async function set(key: string, value: string | object) {
} }
} }
export async function get(key: string, value?: string) { export async function getSimple(key: string) {
const value: any = await redis.get(key);
return value;
}
export async function get({ key, value, parse }: getData) {
if (key.includes("*")) { if (key.includes("*")) {
const keys = await redis.keys(key); const keys = await redis.keys(key);
// If there are keys, delete them
if (keys.length > 0) { if (keys.length > 0) {
for (const key of keys) { for (const key of keys) {
const val = await redis.get(key); const val = await redis.get(key);
if (value == val) return value; if (val.includes(value)) {
if (parse) return JSON.parse(val);
return val;
}
} }
} }
return null; return null;
} else { } else {
const value: any = await redis.get(key); const value: any = await redis.get(key);
return JSON.parse(value);
if (parse) return JSON.parse(value);
return value;
} }
} }

View File

@ -31,6 +31,10 @@ class Message extends Model<Message> {
@Column @Column
fromMe: boolean; fromMe: boolean;
@Default(false)
@Column
fromAgent: boolean;
@Column(DataType.TEXT) @Column(DataType.TEXT)
body: string; body: string;

View File

@ -27,6 +27,7 @@ import { clearAllKeys, get, set } from "./helpers/RedisClient";
import ShowUserService from "./services/UserServices/ShowUserService"; import ShowUserService from "./services/UserServices/ShowUserService";
import { json } from "sequelize"; import { json } from "sequelize";
import { setBotInfo } from "./helpers/SetBotInfo"; import { setBotInfo } from "./helpers/SetBotInfo";
import Queue from "./models/Queue";
const server = app.listen(process.env.PORT, () => { const server = app.listen(process.env.PORT, () => {
logger.info(`Server started on port: ${process.env.PORT}`); logger.info(`Server started on port: ${process.env.PORT}`);
@ -47,7 +48,7 @@ gracefulShutdown(server);
(async () => { (async () => {
console.log("os.tmpdir(): ", os.tmpdir()); console.log("os.tmpdir(): ", os.tmpdir());
await clearAllKeys("user:*", "whatsapp:*"); await clearAllKeys("user:*", "whatsapp:*", "queue:*");
const users = await User.findAll(); const users = await User.findAll();
@ -62,23 +63,37 @@ gracefulShutdown(server);
await set(`user:${id}`, { id, name }); await set(`user:${id}`, { id, name });
} }
// const queues = await Queue.findAll();
// for (const queue of queues) {
// const { id, greetingMessage, name } = queue;
// await set(`queue:${id}`, { id, name, greetingMessage });
// }
loadSettings(); loadSettings();
let whatsapps: any = await Whatsapp.findAll({ let whatsapps: any = await Whatsapp.findAll({
attributes: ["id", "url", "phoneNumberId", "number"] attributes: ["id", "url", "phoneNumberId", "number", "greetingMessage"]
}); });
if (whatsapps && whatsapps.length > 0) { if (whatsapps && whatsapps.length > 0) {
for (let i = 0; i < whatsapps.length; i++) { for (let i = 0; i < whatsapps.length; i++) {
try { try {
const { phoneNumberId } = whatsapps[i]; const { phoneNumberId, id, greetingMessage } = whatsapps[i];
if (phoneNumberId) { if (phoneNumberId) {
await set( await set(
`whatsapp:${whatsapps[i].dataValues.id}`, `whatsapp:${whatsapps[i].dataValues.id}`,
`${whatsapps[i].dataValues.number}` JSON.stringify({
number: whatsapps[i].dataValues.number,
id,
greetingMessage,
phoneNumberId
})
); );
}
if (phoneNumberId) {
continue; continue;
} }

View File

@ -13,6 +13,7 @@ interface MessageData {
read?: boolean; read?: boolean;
mediaType?: string; mediaType?: string;
mediaUrl?: string; mediaUrl?: string;
fromAgent?: boolean;
} }
interface Request { interface Request {
messageData: MessageData; messageData: MessageData;

View File

@ -1,6 +1,7 @@
import * as Yup from "yup"; import * as Yup from "yup";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import { set } from "../../helpers/RedisClient";
interface QueueData { interface QueueData {
name: string; name: string;
@ -9,68 +10,67 @@ interface QueueData {
} }
const CreateQueueService = async (queueData: QueueData): Promise<Queue> => { const CreateQueueService = async (queueData: QueueData): Promise<Queue> => {
try { try {
const { color, name } = queueData; const { color, name } = queueData;
const queueSchema = Yup.object().shape({ const queueSchema = Yup.object().shape({
name: Yup.string() name: Yup.string()
.min(2, "ERR_QUEUE_INVALID_NAME") .min(2, "ERR_QUEUE_INVALID_NAME")
.required("ERR_QUEUE_INVALID_NAME") .required("ERR_QUEUE_INVALID_NAME")
.test( .test(
"Check-unique-name", "Check-unique-name",
"ERR_QUEUE_NAME_ALREADY_EXISTS", "ERR_QUEUE_NAME_ALREADY_EXISTS",
async value => { async value => {
if (value) { if (value) {
const queueWithSameName = await Queue.findOne({ const queueWithSameName = await Queue.findOne({
where: { name: value } where: { name: value }
}); });
return !queueWithSameName; return !queueWithSameName;
}
return false;
}
),
color: Yup.string()
.required("ERR_QUEUE_INVALID_COLOR")
.test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => {
if (value) {
const colorTestRegex = /^#[0-9a-f]{3,6}$/i;
return colorTestRegex.test(value);
} }
return false; return false;
} })
), .test(
color: Yup.string() "Check-color-exists",
.required("ERR_QUEUE_INVALID_COLOR") "ERR_QUEUE_COLOR_ALREADY_EXISTS",
.test("Check-color", "ERR_QUEUE_INVALID_COLOR", async value => { async value => {
if (value) { if (value) {
const colorTestRegex = /^#[0-9a-f]{3,6}$/i; const queueWithSameColor = await Queue.findOne({
return colorTestRegex.test(value); where: { color: value }
} });
return false; return !queueWithSameColor;
}) }
.test( return false;
"Check-color-exists",
"ERR_QUEUE_COLOR_ALREADY_EXISTS",
async value => {
if (value) {
const queueWithSameColor = await Queue.findOne({
where: { color: value }
});
return !queueWithSameColor;
} }
return false; )
} });
)
});
try { try {
await queueSchema.validate({ color, name }); await queueSchema.validate({ color, name });
} catch (err: any) { } catch (err: any) {
throw new AppError(err.message); throw new AppError(err.message);
} }
const queue = await Queue.create(queueData); const queue = await Queue.create(queueData);
return queue; // const { id, greetingMessage } = queue;
// await set(`queue:${id}`, { id, name, greetingMessage });
return queue;
} catch (error: any) { } catch (error: any) {
console.error('===> Error on CreateQueueService.ts file: \n', error) console.error("===> Error on CreateQueueService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default CreateQueueService; export default CreateQueueService;

View File

@ -3,6 +3,7 @@ import * as Yup from "yup";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
import Queue from "../../models/Queue"; import Queue from "../../models/Queue";
import ShowQueueService from "./ShowQueueService"; import ShowQueueService from "./ShowQueueService";
import { set } from "../../helpers/RedisClient"
interface QueueData { interface QueueData {
name?: string; name?: string;
@ -14,9 +15,7 @@ const UpdateQueueService = async (
queueId: number | string, queueId: number | string,
queueData: QueueData queueData: QueueData
): Promise<Queue> => { ): Promise<Queue> => {
try { try {
const { color, name } = queueData; const { color, name } = queueData;
const queueSchema = Yup.object().shape({ const queueSchema = Yup.object().shape({
@ -70,13 +69,14 @@ const UpdateQueueService = async (
await queue.update(queueData); await queue.update(queueData);
return queue; // const { id, greetingMessage } = queue;
// await set(`queue:${id}`, { id, name, greetingMessage });
return queue;
} catch (error: any) { } catch (error: any) {
console.error('===> Error on UpdateQueueService.ts file: \n', error) console.error("===> Error on UpdateQueueService.ts file: \n", error);
throw new AppError(error.message); throw new AppError(error.message);
} }
}; };
export default UpdateQueueService; export default UpdateQueueService;

View File

@ -31,8 +31,6 @@ const CheckIsValidContact = async (
if (!isValidNumber) { if (!isValidNumber) {
console.log('kkkkkkkkkkkkkkkkkkkkkkkkkkkkk ')
const { data, status } = await axios.post( const { data, status } = await axios.post(
`${process.env.WHATS_NUMBER_VALIDATOR_URL}/api/validate`, `${process.env.WHATS_NUMBER_VALIDATOR_URL}/api/validate`,
{ mobile: number }, { mobile: number },

View File

@ -92,7 +92,8 @@ import {
createObject, createObject,
findByContain, findByContain,
findObject, findObject,
get get,
getSimple
} from "../../helpers/RedisClient"; } from "../../helpers/RedisClient";
import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot"; import FindOrCreateTicketServiceBot from "../TicketServices/FindOrCreateTicketServiceBot";
import ShowTicketService from "../TicketServices/ShowTicketService"; import ShowTicketService from "../TicketServices/ShowTicketService";
@ -175,9 +176,14 @@ const verifyMediaMessage = async (
mediaUrl: media.filename, mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0], mediaType: media.mimetype.split("/")[0],
quotedMsgId: quotedMsg, quotedMsgId: quotedMsg,
phoneNumberId: msg?.phoneNumberId phoneNumberId: msg?.phoneNumberId,
fromAgent: false
}; };
if (msg?.fromMe) {
messageData = { ...messageData, fromAgent: true };
}
if (!ticket?.phoneNumberId) { if (!ticket?.phoneNumberId) {
if (!media.filename) { if (!media.filename) {
const ext = media.mimetype.split("/")[1].split(";")[0]; const ext = media.mimetype.split("/")[1].split(";")[0];
@ -280,18 +286,33 @@ const verifyMessage = async (
contact: Contact, contact: Contact,
quotedMsg?: any quotedMsg?: any
) => { ) => {
const messageData = { let messageData = {
id: msg.id.id, id: msg.id.id,
ticketId: ticket.id, ticketId: ticket.id,
contactId: msg.fromMe ? undefined : contact.id, contactId: msg.fromMe ? undefined : contact.id,
body: msg.body, body: msg.body,
fromMe: msg.fromMe, fromMe: msg.fromMe,
fromAgent: false,
mediaType: msg.type, mediaType: msg.type,
read: msg.fromMe, read: msg.fromMe,
quotedMsgId: quotedMsg, quotedMsgId: quotedMsg,
phoneNumberId: msg?.phoneNumberId phoneNumberId: msg?.phoneNumberId
}; };
if (msg?.fromMe) {
const botInfo = await BotIsOnQueue("botqueue");
if (botInfo.isOnQueue) {
const ura: any = await get({ key: "ura" });
if (ura && !ura.includes(JSON.stringify(msg?.body))) {
messageData = { ...messageData, fromAgent: true };
}
} else if (msg?.body?.trim().length > 0 && !/\u200e/.test(msg.body[0])) {
messageData = { ...messageData, fromAgent: true };
}
}
await ticket.update({ lastMessage: msg.body }); await ticket.update({ lastMessage: msg.body });
await CreateMessageService({ messageData }); await CreateMessageService({ messageData });
@ -349,7 +370,7 @@ const verifyQueue = async (
ticketId: ticket.id ticketId: ticket.id
}); });
const data = await get("ura"); const data = await get({ key: "ura", parse: true });
await createObject({ await createObject({
whatsappId: `${ticket.whatsappId}`, whatsappId: `${ticket.whatsappId}`,
@ -965,7 +986,7 @@ const handleMessage = async (
const menu = async (userTyped: string, whatsappId: any, contactId: any) => { const menu = async (userTyped: string, whatsappId: any, contactId: any) => {
let lastId = await findObject(whatsappId, contactId, "ura"); let lastId = await findObject(whatsappId, contactId, "ura");
const data: any = await get("ura"); const data: any = await get({ key: "ura", parse: true });
console.log("lastId[0]: ", lastId[0]); console.log("lastId[0]: ", lastId[0]);