import { prismaRepository } from '@api/server.module'; import { configService, Database } from '@config/env.config'; import { Logger } from '@config/logger.config'; import dayjs from 'dayjs'; const logger = new Logger('OnWhatsappCache'); function getAvailableNumbers(remoteJid: string) { const numbersAvailable: string[] = []; if (remoteJid.startsWith('+')) { remoteJid = remoteJid.slice(1); } const [number, domain] = remoteJid.split('@'); // TODO: Se já for @lid, retornar apenas ele mesmo SEM adicionar @domain novamente if (domain === 'lid' || domain === 'g.us') { return [remoteJid]; // Retorna direto para @lid e @g.us } // Brazilian numbers if (remoteJid.startsWith('55')) { const numberWithDigit = number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`; const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5); numbersAvailable.push(numberWithDigit); numbersAvailable.push(numberWithoutDigit); } // Mexican/Argentina numbers // Ref: https://faq.whatsapp.com/1294841057948784 else if (number.startsWith('52') || number.startsWith('54')) { let prefix = ''; if (number.startsWith('52')) { prefix = '1'; } if (number.startsWith('54')) { prefix = '9'; } const numberWithDigit = number.slice(2, 3) === prefix && number.length === 13 ? number : `${number.slice(0, 2)}${prefix}${number.slice(2)}`; const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3); numbersAvailable.push(numberWithDigit); numbersAvailable.push(numberWithoutDigit); } // Other countries else { numbersAvailable.push(remoteJid); } // TODO: Adiciona @domain apenas para números que não são @lid return numbersAvailable.map((number) => `${number}@${domain}`); } interface ISaveOnWhatsappCacheParams { remoteJid: string; remoteJidAlt?: string; lid?: 'lid' | undefined; } function normalizeJid(jid: string | null | undefined): string | null { if (!jid) return null; return jid.startsWith('+') ? jid.slice(1) : jid; } export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) { if (!configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { return; } // Processa todos os itens em paralelo para melhor performance const processingPromises = data.map(async (item) => { try { const remoteJid = normalizeJid(item.remoteJid); if (!remoteJid) { logger.warn('[saveOnWhatsappCache] Item skipped, missing remoteJid.'); return; } const altJidNormalized = normalizeJid(item.remoteJidAlt); const lidAltJid = altJidNormalized && altJidNormalized.includes('@lid') ? altJidNormalized : null; const baseJids = [remoteJid]; // Garante que o remoteJid esteja na lista inicial if (lidAltJid) { baseJids.push(lidAltJid); } const expandedJids = baseJids.flatMap((jid) => getAvailableNumbers(jid)); // 1. Busca entrada por jidOptions e também remoteJid // Às vezes acontece do remoteJid atual NÃO ESTAR no jidOptions ainda, ocasionando o erro: // 'Unique constraint failed on the fields: (`remoteJid`)' // Isso acontece principalmente em grupos que possuem o número do criador no ID (ex.: '559911223345-1234567890@g.us') const existingRecord = await prismaRepository.isOnWhatsapp.findFirst({ where: { OR: [ ...expandedJids.map((jid) => ({ jidOptions: { contains: jid } })), { remoteJid: remoteJid }, // TODO: Descobrir o motivo que causa o remoteJid não estar (às vezes) incluso na lista de jidOptions ], }, }); logger.verbose( `[saveOnWhatsappCache] Register exists for [${expandedJids.join(',')}]? => ${existingRecord ? existingRecord.remoteJid : 'Not found'}`, ); // 2. Unifica todos os JIDs usando um Set para garantir valores únicos const finalJidOptions = new Set(expandedJids); if (lidAltJid) { finalJidOptions.add(lidAltJid); } if (existingRecord?.jidOptions) { existingRecord.jidOptions.split(',').forEach((jid) => finalJidOptions.add(jid)); } // 3. Prepara o payload final // Ordena os JIDs para garantir consistência na string final const sortedJidOptions = [...finalJidOptions].sort(); const newJidOptionsString = sortedJidOptions.join(','); const newLid = item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null; const dataPayload = { remoteJid: remoteJid, jidOptions: newJidOptionsString, lid: newLid, }; // 4. Decide entre Criar ou Atualizar if (existingRecord) { // Compara a string de JIDs ordenada existente com a nova const existingJidOptionsString = existingRecord.jidOptions ? existingRecord.jidOptions.split(',').sort().join(',') : ''; const isDataSame = existingRecord.remoteJid === dataPayload.remoteJid && existingJidOptionsString === dataPayload.jidOptions && existingRecord.lid === dataPayload.lid; if (isDataSame) { logger.verbose(`[saveOnWhatsappCache] Data for ${remoteJid} is already up-to-date. Skipping update.`); return; // Pula para o próximo item } // Os dados são diferentes, então atualiza logger.verbose( `[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, ); await prismaRepository.isOnWhatsapp.update({ where: { id: existingRecord.id }, data: dataPayload, }); } else { // Cria nova entrada logger.verbose( `[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`, ); await prismaRepository.isOnWhatsapp.create({ data: dataPayload, }); } } catch (e) { // Loga o erro mas não para a execução dos outros promises logger.error(`[saveOnWhatsappCache] Error processing item for ${item.remoteJid}: `, e); } }); // Espera todas as operações paralelas terminarem await Promise.allSettled(processingPromises); } export async function getOnWhatsappCache(remoteJids: string[]) { let results: { remoteJid: string; number: string; jidOptions: string[]; lid?: string; }[] = []; if (configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) { const remoteJidsWithoutPlus = remoteJids.map((remoteJid) => getAvailableNumbers(remoteJid)).flat(); const onWhatsappCache = await prismaRepository.isOnWhatsapp.findMany({ where: { OR: remoteJidsWithoutPlus.map((remoteJid) => ({ jidOptions: { contains: remoteJid } })), updatedAt: { gte: dayjs().subtract(configService.get('DATABASE').SAVE_DATA.IS_ON_WHATSAPP_DAYS, 'days').toDate(), }, }, }); results = onWhatsappCache.map((item) => ({ remoteJid: item.remoteJid, number: item.remoteJid.split('@')[0], jidOptions: item.jidOptions.split(','), lid: item.lid, })); } return results; }