feat(IsOnWhatsapp): add optional lid field and update related logic

- Introduced a new optional `lid` field in the IsOnWhatsapp model to enhance data tracking.
- Updated migration script to add the `lid` column to the database.
- Modified OnWhatsAppDto to include the `lid` property for better integration with WhatsApp user data.
- Enhanced the WhatsApp Baileys service to handle `lid` numbers separately and improve user verification logic.
- Updated cache handling functions to support the new `lid` field for consistent data management.
This commit is contained in:
Davidson Gomes 2025-06-13 11:52:32 -03:00
parent c17b48bca0
commit afc2927837
6 changed files with 138 additions and 76 deletions

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "IsOnWhatsapp" ADD COLUMN "lid" VARCHAR(100);

View File

@ -648,6 +648,7 @@ model IsOnWhatsapp {
id String @id @default(cuid()) id String @id @default(cuid())
remoteJid String @unique @db.VarChar(100) remoteJid String @unique @db.VarChar(100)
jidOptions String jidOptions String
lid String? @db.VarChar(100)
createdAt DateTime @default(now()) @db.Timestamp createdAt DateTime @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp
} }

View File

@ -13,6 +13,7 @@ export class OnWhatsAppDto {
public readonly exists: boolean, public readonly exists: boolean,
public readonly number: string, public readonly number: string,
public readonly name?: string, public readonly name?: string,
public readonly lid?: string,
) {} ) {}
} }

View File

@ -1170,7 +1170,7 @@ export class BaileysStartupService extends ChannelStartupService {
) { ) {
const chatwootSentMessage = await this.chatwootService.eventWhatsapp( const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
Events.MESSAGES_UPSERT, Events.MESSAGES_UPSERT,
{ instanceName: this.instance.name, instanceId: this.instance.id }, { instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw, messageRaw,
); );
@ -3131,10 +3131,10 @@ export class BaileysStartupService extends ChannelStartupService {
const group = await this.findGroup({ groupJid: jid }, 'inner'); const group = await this.findGroup({ groupJid: jid }, 'inner');
if (!group) { if (!group) {
new OnWhatsAppDto(jid, false, number); return new OnWhatsAppDto(jid, false, number);
} }
return new OnWhatsAppDto(group.id, !!group?.id, number, group?.subject); return new OnWhatsAppDto(group.id, true, number, group?.subject);
}), }),
); );
onWhatsapp.push(...groups); onWhatsapp.push(...groups);
@ -3144,84 +3144,126 @@ export class BaileysStartupService extends ChannelStartupService {
where: { instanceId: this.instanceId, remoteJid: { in: jids.users.map(({ jid }) => jid) } }, where: { instanceId: this.instanceId, remoteJid: { in: jids.users.map(({ jid }) => jid) } },
}); });
const numbersToVerify = jids.users.map(({ jid }) => jid.replace('+', '')); // Separate @lid numbers from normal numbers
const lidUsers = jids.users.filter(({ jid }) => jid.includes('@lid'));
const normalUsers = jids.users.filter(({ jid }) => !jid.includes('@lid'));
const cachedNumbers = await getOnWhatsappCache(numbersToVerify); // For normal numbers, use traditional Baileys verification
const filteredNumbers = numbersToVerify.filter( let normalVerifiedUsers: OnWhatsAppDto[] = [];
(jid) => !cachedNumbers.some((cached) => cached.jidOptions.includes(jid)), if (normalUsers.length > 0) {
); console.log('normalUsers', normalUsers);
const numbersToVerify = normalUsers.map(({ jid }) => jid.replace('+', ''));
console.log('numbersToVerify', numbersToVerify);
const verify = await this.client.onWhatsApp(...filteredNumbers); const cachedNumbers = await getOnWhatsappCache(numbersToVerify);
const users: OnWhatsAppDto[] = await Promise.all( console.log('cachedNumbers', cachedNumbers);
jids.users.map(async (user) => {
let numberVerified: (typeof verify)[0] | null = null;
const cached = cachedNumbers.find((cached) => cached.jidOptions.includes(user.jid.replace('+', ''))); const filteredNumbers = numbersToVerify.filter(
if (cached) { (jid) => !cachedNumbers.some((cached) => cached.jidOptions.includes(jid)),
return { );
exists: true, console.log('filteredNumbers', filteredNumbers);
jid: cached.remoteJid,
name: contacts.find((c) => c.remoteJid === cached.remoteJid)?.pushName,
number: user.number,
};
}
// Brazilian numbers const verify = await this.client.onWhatsApp(...filteredNumbers);
if (user.number.startsWith('55')) { console.log('verify', verify);
const numberWithDigit = normalVerifiedUsers = await Promise.all(
user.number.slice(4, 5) === '9' && user.number.length === 13 normalUsers.map(async (user) => {
? user.number let numberVerified: (typeof verify)[0] | null = null;
: `${user.number.slice(0, 4)}9${user.number.slice(4)}`;
const numberWithoutDigit =
user.number.length === 12 ? user.number : user.number.slice(0, 4) + user.number.slice(5);
numberVerified = verify.find( const cached = cachedNumbers.find((cached) => cached.jidOptions.includes(user.jid.replace('+', '')));
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`, if (cached) {
); return new OnWhatsAppDto(
} cached.remoteJid,
true,
// Mexican/Argentina numbers user.number,
// Ref: https://faq.whatsapp.com/1294841057948784 contacts.find((c) => c.remoteJid === cached.remoteJid)?.pushName,
if (!numberVerified && (user.number.startsWith('52') || user.number.startsWith('54'))) { cached.lid || (cached.remoteJid.includes('@lid') ? cached.remoteJid.split('@')[1] : undefined),
let prefix = ''; );
if (user.number.startsWith('52')) {
prefix = '';
}
if (user.number.startsWith('54')) {
prefix = '9';
} }
const numberWithDigit = // Brazilian numbers
user.number.slice(2, 3) === prefix && user.number.length === 13 if (user.number.startsWith('55')) {
? user.number const numberWithDigit =
: `${user.number.slice(0, 2)}${prefix}${user.number.slice(2)}`; user.number.slice(4, 5) === '9' && user.number.length === 13
const numberWithoutDigit = ? user.number
user.number.length === 12 ? user.number : user.number.slice(0, 2) + user.number.slice(3); : `${user.number.slice(0, 4)}9${user.number.slice(4)}`;
const numberWithoutDigit =
user.number.length === 12 ? user.number : user.number.slice(0, 4) + user.number.slice(5);
numberVerified = verify.find( numberVerified = verify.find(
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`, (v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
);
}
// Mexican/Argentina numbers
// Ref: https://faq.whatsapp.com/1294841057948784
if (!numberVerified && (user.number.startsWith('52') || user.number.startsWith('54'))) {
let prefix = '';
if (user.number.startsWith('52')) {
prefix = '1';
}
if (user.number.startsWith('54')) {
prefix = '9';
}
const numberWithDigit =
user.number.slice(2, 3) === prefix && user.number.length === 13
? user.number
: `${user.number.slice(0, 2)}${prefix}${user.number.slice(2)}`;
const numberWithoutDigit =
user.number.length === 12 ? user.number : user.number.slice(0, 2) + user.number.slice(3);
numberVerified = verify.find(
(v) => v.jid === `${numberWithDigit}@s.whatsapp.net` || v.jid === `${numberWithoutDigit}@s.whatsapp.net`,
);
}
if (!numberVerified) {
numberVerified = verify.find((v) => v.jid === user.jid);
}
const numberJid = numberVerified?.jid || user.jid;
const lid =
typeof numberVerified?.lid === 'string'
? numberVerified.lid
: numberJid.includes('@lid')
? numberJid.split('@')[1]
: undefined;
return new OnWhatsAppDto(
numberJid,
!!numberVerified?.exists,
user.number,
contacts.find((c) => c.remoteJid === numberJid)?.pushName,
lid,
); );
} }),
);
}
if (!numberVerified) { // For @lid numbers, always consider them as valid
numberVerified = verify.find((v) => v.jid === user.jid); const lidVerifiedUsers: OnWhatsAppDto[] = lidUsers.map((user) => {
} return new OnWhatsAppDto(
user.jid,
true,
user.number,
contacts.find((c) => c.remoteJid === user.jid)?.pushName,
user.jid.split('@')[1],
);
});
const numberJid = numberVerified?.jid || user.jid; // Combine results
onWhatsapp.push(...normalVerifiedUsers, ...lidVerifiedUsers);
return { // Save to cache only valid numbers
exists: !!numberVerified?.exists, await saveOnWhatsappCache(
jid: numberJid, onWhatsapp
name: contacts.find((c) => c.remoteJid === numberJid)?.pushName, .filter((user) => user.exists)
number: user.number, .map((user) => ({
}; remoteJid: user.jid,
}), jidOptions: user.jid.replace('+', ''),
lid: user.lid,
})),
); );
await saveOnWhatsappCache(users.filter((user) => user.exists).map((user) => ({ remoteJid: user.jid })));
onWhatsapp.push(...users);
return onWhatsapp; return onWhatsapp;
} }

View File

@ -46,14 +46,19 @@ export class ChatRouter extends RouterBroker {
super(); super();
this.router this.router
.post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => { .post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => {
const response = await this.dataValidate<WhatsAppNumberDto>({ try {
request: req, const response = await this.dataValidate<WhatsAppNumberDto>({
schema: whatsappNumberSchema, request: req,
ClassRef: WhatsAppNumberDto, schema: whatsappNumberSchema,
execute: (instance, data) => chatController.whatsappNumber(instance, data), ClassRef: WhatsAppNumberDto,
}); execute: (instance, data) => chatController.whatsappNumber(instance, data),
});
return res.status(HttpStatus.OK).json(response); return res.status(HttpStatus.OK).json(response);
} catch (error) {
console.log(error);
return res.status(HttpStatus.BAD_REQUEST).json(error);
}
}) })
.post(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { .post(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
const response = await this.dataValidate<ReadMessageDto>({ const response = await this.dataValidate<ReadMessageDto>({

View File

@ -52,7 +52,9 @@ function getAvailableNumbers(remoteJid: string) {
interface ISaveOnWhatsappCacheParams { interface ISaveOnWhatsappCacheParams {
remoteJid: string; remoteJid: string;
lid?: string;
} }
export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) { export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
const upsertsQuery = data.map((item) => { const upsertsQuery = data.map((item) => {
@ -60,8 +62,15 @@ export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
const numbersAvailable = getAvailableNumbers(remoteJid); const numbersAvailable = getAvailableNumbers(remoteJid);
return prismaRepository.isOnWhatsapp.upsert({ return prismaRepository.isOnWhatsapp.upsert({
create: { remoteJid: remoteJid, jidOptions: numbersAvailable.join(',') }, create: {
update: { jidOptions: numbersAvailable.join(',') }, remoteJid: remoteJid,
jidOptions: numbersAvailable.join(','),
lid: item.lid,
},
update: {
jidOptions: numbersAvailable.join(','),
lid: item.lid,
},
where: { remoteJid: remoteJid }, where: { remoteJid: remoteJid },
}); });
}); });
@ -75,6 +84,7 @@ export async function getOnWhatsappCache(remoteJids: string[]) {
remoteJid: string; remoteJid: string;
number: string; number: string;
jidOptions: string[]; jidOptions: string[];
lid?: string;
}[] = []; }[] = [];
if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
@ -93,6 +103,7 @@ export async function getOnWhatsappCache(remoteJids: string[]) {
remoteJid: item.remoteJid, remoteJid: item.remoteJid,
number: item.remoteJid.split('@')[0], number: item.remoteJid.split('@')[0],
jidOptions: item.jidOptions.split(','), jidOptions: item.jidOptions.split(','),
lid: item.lid,
})); }));
} }