-
Notifications
You must be signed in to change notification settings - Fork 5.2k
fix: implementa atualização automática de contatos @lid #1845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
4ee4ee3
fix: implementa resolução automática de contatos @lid
ceb946c
fix: build error
5013e51
Update src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
ricaelchiquetti 70a2c18
Merge branch 'release/2.3.2'
DavidsonGomes c2085b5
Merge branch 'release/2.3.2'
DavidsonGomes da8f22c
Merge branch 'EvolutionAPI:main' into main
ricaelchiquetti b7b3f1c
feat: implement standardized error handling for WhatsApp API responses
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -670,6 +670,8 @@ | |
|
|
||
| this.eventHandler(); | ||
|
|
||
| this.startLidCleanupScheduler(); | ||
|
|
||
| this.client.ws.on('CB:call', (packet) => { | ||
| console.log('CB:call', packet); | ||
| const payload = { event: 'CB:call', packet: packet }; | ||
|
|
@@ -1049,8 +1051,10 @@ | |
| try { | ||
| for (const received of messages) { | ||
| if (received.key.remoteJid?.includes('@lid') && received.key.senderPn) { | ||
| (received.key as { previousRemoteJid?: string | null }).previousRemoteJid = received.key.remoteJid; | ||
| this.logger.verbose(`Processing @lid message: ${received.key.remoteJid} -> ${received.key.senderPn}`); | ||
| const previousRemoteJid = received.key.remoteJid; | ||
| received.key.remoteJid = received.key.senderPn; | ||
| await this.updateContactFromLid(previousRemoteJid, received.key.remoteJid); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (performance): Nested loop over keys may be redundant and impact performance. The inner loop over 'keys' may cause unnecessary repeated work. Consider refactoring to avoid double iteration. |
||
| if ( | ||
| received?.messageStubParameters?.some?.((param) => | ||
|
|
@@ -3991,6 +3995,221 @@ | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Atualiza contatos que foram criados com @lid para o JID real | ||
| * Isso resolve problemas de mensagens não chegando no iPhone | ||
| * Funciona com ou sem banco de dados | ||
| */ | ||
| private async updateContactFromLid(lidJid: string, realJid: string) { | ||
| try { | ||
| // Verificar se o banco de dados está habilitado | ||
| const db = this.configService.get<Database>('DATABASE'); | ||
| const cache = this.configService.get<CacheConf>('CACHE'); | ||
|
|
||
| if (db.SAVE_DATA.CONTACTS) { | ||
| // Com banco de dados - usar Prisma | ||
| try { | ||
| // Buscar contato com @lid | ||
| const lidContact = await this.prismaRepository.contact.findFirst({ | ||
| where: { | ||
| remoteJid: lidJid, | ||
| instanceId: this.instanceId, | ||
| }, | ||
| }); | ||
|
|
||
| if (lidContact) { | ||
| // Atualizar para o JID real | ||
| await this.prismaRepository.contact.update({ | ||
| where: { id: lidContact.id }, | ||
| data: { remoteJid: realJid }, | ||
| }); | ||
|
|
||
| this.logger.verbose(`Updated contact from @lid: ${lidJid} -> ${realJid}`); | ||
| } | ||
|
|
||
| // Também atualizar mensagens com @lid | ||
| const lidMessages = await this.prismaRepository.message.findMany({ | ||
| where: { | ||
| instanceId: this.instanceId, | ||
| key: { | ||
| path: ['remoteJid'], | ||
| equals: lidJid, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| if (lidMessages.length > 0) { | ||
| for (const message of lidMessages) { | ||
| const key = message.key as any; | ||
| key.remoteJid = realJid; | ||
|
|
||
| await this.prismaRepository.message.update({ | ||
| where: { id: message.id }, | ||
| data: { key: key }, | ||
| }); | ||
| } | ||
|
|
||
| this.logger.verbose(`Updated ${lidMessages.length} messages from @lid: ${lidJid} -> ${realJid}`); | ||
| } | ||
| } catch (dbError) { | ||
| this.logger.warn(`Database operation failed, falling back to cache: ${dbError.message}`); | ||
| } | ||
| } | ||
|
|
||
| // Sem banco de dados - usar cache e arquivos locais | ||
| if (cache?.REDIS?.ENABLED) { | ||
| // Atualizar no cache Redis | ||
| try { | ||
| const cacheKey = `contact:${this.instanceId}:${lidJid}`; | ||
| const realContactKey = `contact:${this.instanceId}:${realJid}`; | ||
|
|
||
| // Buscar dados do contato @lid no cache | ||
| const lidContactData = await this.cache.hGet(this.instanceId, cacheKey); | ||
| if (lidContactData) { | ||
| // Atualizar para o JID real no cache | ||
| await this.cache.hSet(this.instanceId, realContactKey, lidContactData); | ||
| await this.cache.hDelete(this.instanceId, cacheKey); | ||
|
|
||
| this.logger.verbose(`Updated Redis cache contact from @lid: ${lidJid} -> ${realJid}`); | ||
| } | ||
| } catch (cacheError) { | ||
| this.logger.warn(`Redis cache operation failed: ${cacheError.message}`); | ||
| } | ||
| } | ||
|
|
||
| // Atualizar arquivos locais se necessário | ||
| if (this.instance.authState) { | ||
| try { | ||
| // Atualizar o estado de autenticação local | ||
| const authState = this.instance.authState as any; | ||
| if (authState.store && authState.store.contacts) { | ||
| // Atualizar contatos no store local | ||
| const {contacts} = authState.store; | ||
| if (contacts[lidJid]) { | ||
| contacts[realJid] = contacts[lidJid]; | ||
| delete contacts[lidJid]; | ||
|
|
||
| this.logger.verbose(`Updated local auth state contact from @lid: ${lidJid} -> ${realJid}`); | ||
| } | ||
| } | ||
| } catch (localError) { | ||
| this.logger.warn(`Local auth state update failed: ${localError.message}`); | ||
| } | ||
| } | ||
|
|
||
| this.logger.info(`Successfully processed @lid update: ${lidJid} -> ${realJid}`); | ||
| } catch (error) { | ||
| this.logger.error(`Error updating contact from @lid: ${lidJid}`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Limpa contatos @lid órfãos e faz manutenção periódica | ||
| * Executa automaticamente para resolver problemas de mensagens não chegando | ||
| */ | ||
| private async cleanupOrphanedLidContacts() { | ||
| try { | ||
| this.logger.verbose('Starting cleanup of orphaned @lid contacts...'); | ||
|
|
||
| const db = this.configService.get<Database>('DATABASE'); | ||
| const cache = this.configService.get<CacheConf>('CACHE'); | ||
|
|
||
| if (db.SAVE_DATA.CONTACTS) { | ||
| // Com banco: buscar todos os contatos @lid | ||
| try { | ||
| const lidContacts = await this.prismaRepository.contact.findMany({ | ||
| where: { | ||
| remoteJid: { contains: '@lid' }, | ||
| instanceId: this.instanceId, | ||
| }, | ||
| }); | ||
|
|
||
| this.logger.verbose(`Found ${lidContacts.length} @lid contacts to cleanup`); | ||
|
|
||
| for (const contact of lidContacts) { | ||
| // Tentar resolver o JID real através do WhatsApp | ||
| try { | ||
| // Usar o cliente WhatsApp para verificar se o contato existe | ||
| const contactInfo = await this.client.onWhatsApp(contact.remoteJid); | ||
| if (contactInfo && contactInfo.length > 0 && contactInfo[0].jid && !contactInfo[0].jid.includes('@lid')) { | ||
| // Contato foi resolvido, atualizar | ||
| await this.updateContactFromLid(contact.remoteJid, contactInfo[0].jid); | ||
| } else { | ||
| // Contato não pode ser resolvido, remover | ||
| this.logger.warn(`Removing orphaned @lid contact: ${contact.remoteJid}`); | ||
| await this.prismaRepository.contact.delete({ | ||
| where: { id: contact.id } | ||
| }); | ||
| } | ||
| } catch (contactError) { | ||
| this.logger.warn(`Could not resolve contact ${contact.remoteJid}: ${contactError.message}`); | ||
| } | ||
| } | ||
| } catch (dbError) { | ||
| this.logger.warn(`Database cleanup failed: ${dbError.message}`); | ||
| } | ||
| } | ||
|
|
||
| // Limpeza de cache Redis | ||
| if (cache?.REDIS?.ENABLED) { | ||
| try { | ||
| const keys = await this.cache.keys('*@lid*'); | ||
| this.logger.verbose(`Found ${keys.length} @lid keys in Redis cache`); | ||
|
|
||
| for (const key of keys) { | ||
| // Tentar resolver e atualizar | ||
| const contactData = await this.cache.hGet(this.instanceId, key); | ||
| if (contactData) { | ||
| this.logger.verbose(`Processing Redis cache key: ${key}`); | ||
| for (const key of keys) { | ||
| const contactData = await this.cache.hGet(this.instanceId, key); | ||
| if (contactData) { | ||
| try { | ||
| // Extrai o JID @lid da chave do cache | ||
| const lidJid = key.split(':').pop(); | ||
| // Usa o Baileys para tentar resolver o JID real | ||
| const contactInfo = await this.client.onWhatsApp(lidJid); | ||
| if (contactInfo && contactInfo.length > 0 && contactInfo[0].jid && !contactInfo[0].jid.includes('@lid')) { | ||
| // Atualiza o cache para o JID real | ||
| const realContactKey = `contact:${this.instanceId}:${contactInfo[0].jid}`; | ||
| await this.cache.hSet(this.instanceId, realContactKey, contactData); | ||
| await this.cache.hDelete(this.instanceId, key); | ||
| this.logger.verbose(`Updated Redis cache contact from @lid: ${key} -> ${realContactKey}`); | ||
| } | ||
| } catch (resolveError) { | ||
| this.logger.warn(`Could not resolve contact in cache: ${key} - ${resolveError.message}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } catch (cacheError) { | ||
| this.logger.warn(`Redis cleanup failed: ${cacheError.message}`); | ||
| } | ||
| } | ||
|
|
||
| this.logger.info('Completed cleanup of orphaned @lid contacts'); | ||
| } catch (error) { | ||
| this.logger.error(`Error during @lid cleanup`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Inicia o processo de limpeza periódica de @lid | ||
| * Executa a cada 5 minutos para manter o sistema limpo | ||
| */ | ||
| private startLidCleanupScheduler() { | ||
| // Limpeza inicial | ||
| setTimeout(() => this.cleanupOrphanedLidContacts(), 30000); // 30 segundos após inicialização | ||
|
|
||
| // Limpeza periódica a cada 5 minutos | ||
| setInterval(() => { | ||
| this.cleanupOrphanedLidContacts(); | ||
| }, 5 * 60 * 1000); // 5 minutos | ||
|
|
||
| this.logger.info('Started periodic @lid cleanup scheduler (every 5 minutes)'); | ||
| } | ||
|
|
||
| private getGroupMetadataCache = async (groupJid: string) => { | ||
| if (!isJidGroup(groupJid)) return null; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider making the lid cleanup scheduler opt-in or configurable.
Some deployments may require tighter control over resource usage or side effects, so providing a configuration option to enable or disable the scheduler would improve flexibility.
Suggested implementation:
configobject or property. If not, you will need to add it to the constructor or initialization logic.enableLidCleanupScheduler) in your configuration schema or documentation.