feat(chats): sincroniza chats fixados (pin/arquivo) via app-state

Pin/arquivo/mute não vêm no history sync nem em chats.upsert — vivem no
app-state do WhatsApp. Adiciona syncChatStates() que faz resyncAppState das
collections critical_unblock_low/regular_high/regular (isoladas: uma falha não
derruba as outras); os pins chegam como chats.update e já são aplicados em
isPinned, subindo os fixados ao topo (ordenação já existente). Roda no
pós-conexão e reexecuta (3s/15s/40s) pois as app-state keys chegam após parear.
Obs.: depende da engine 'official' (a InfiniteAPI não sincroniza app-state).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-06-28 00:53:57 +02:00
parent 9d2412df21
commit e980097fbe
2 changed files with 36 additions and 0 deletions
@@ -620,6 +620,11 @@ export class WhatsAppConnectionManager {
})
.catch(() => {})
// FIXADOS PRIMEIRO: puxa pin/arquivo/mute (app-state) logo e reexecuta,
// pois as app-state keys podem chegar alguns segundos após o pareamento.
setTimeout(() => handler.syncChatStates().catch(() => {}), 3000)
setTimeout(() => handler.syncChatStates().catch(() => {}), 15_000)
setTimeout(() => handler.syncChatStates().catch(() => {}), 40_000)
// Busca avatares ausentes após 5s (dá tempo para o sync inicial)
setTimeout(() => handler.syncMissingAvatars().catch(() => {}), 5000)
// Renova avatares próximos de expirar após 30s (não compete com syncMissing)
@@ -385,6 +385,37 @@ export class ContactHandler {
}
}
/**
* Puxa o estado de PIN / ARQUIVO / MUTE dos chats via app-state do WhatsApp.
*
* Esses estados NÃO vêm no history sync comum nem em chats.upsert — vivem na
* collection 'regular_high' do app-state. O resyncAppState força o telefone a
* reenviá-los; chegam como chats.update {pinned/archived} e são aplicados por
* handleChatsUpdate. Sem isto, os chats fixados nunca sobem ao topo da lista.
*/
async syncChatStates(): Promise<void> {
if (!this.sock) return
// Pin/arquivo/mute podem viver em diferentes collections do app-state.
// Tenta cada uma ISOLADAMENTE: algumas falham com "tried remove, but no
// previous op" (estado parcial) — isolar evita que uma derrube as demais.
// 'regular_high' é a principal para pin/mute; as outras cobrem variações.
const collections = ['critical_unblock_low', 'regular_high', 'regular']
let ok = 0
for (const collection of collections) {
try {
await this.sock.resyncAppState([collection] as any, false)
ok++
logger.info({ instanceId: this.instanceId, collection },
'[ContactHandler] app-state sincronizado (pin/arquivo/mute)')
} catch (err) {
logger.warn({ err, instanceId: this.instanceId, collection },
'[ContactHandler] Falha ao sincronizar collection do app-state (não-fatal)')
}
}
logger.info({ instanceId: this.instanceId, collectionsOk: ok },
'[ContactHandler] Sync de pin/arquivo dos chats concluído')
}
// ═══════════════════════════════════════════════════════════════════════════
// SYNC PÓS-CONEXÃO: AVATARES AUSENTES
// ═══════════════════════════════════════════════════════════════════════════