🐛 Fix contact sync losing phone/email data

Contact entries were fragmented during sync - same person indexed
separately by phone and email, with deduplication keeping only one.

Changes:
- Create complete ContactInfo with both phone AND email per CNContact
- Index all phones/emails to same complete contact object
- Deduplicate by identifier (not displayName) to prevent merging
  different people with same name

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-30 04:43:44 -08:00
parent 9b7ff88779
commit 499ccab2e2

View file

@ -82,6 +82,7 @@ class iMessageReader {
// Fetch all contacts with phone numbers and emails
let keysToFetch: [CNKeyDescriptor] = [
CNContactIdentifierKey as CNKeyDescriptor,
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
@ -96,27 +97,30 @@ class iMessageReader {
guard !fullName.isEmpty else { return }
// Index by phone numbers
for phone in contact.phoneNumbers {
let normalized = self.normalizePhoneNumber(phone.value.stringValue)
self.contactCache[normalized] = ContactInfo(
identifier: normalized,
displayName: fullName,
phoneNumber: normalized,
email: nil
)
// Get all phones and emails for this single contact
let phones = contact.phoneNumbers.map { self.normalizePhoneNumber($0.value.stringValue) }
let emails = contact.emailAddresses.map { ($0.value as String).lowercased() }
// Create a complete ContactInfo with first phone/email (for sync)
let completeContact = ContactInfo(
identifier: phones.first ?? emails.first ?? contact.identifier,
displayName: fullName,
phoneNumber: phones.first,
email: emails.first
)
// Index by all phones (all point to same complete contact)
for phone in phones {
self.contactCache[phone] = completeContact
}
// Index by emails
for email in contact.emailAddresses {
let emailStr = email.value as String
self.contactCache[emailStr.lowercased()] = ContactInfo(
identifier: emailStr,
displayName: fullName,
phoneNumber: nil,
email: emailStr
)
// Index by all emails (all point to same complete contact)
for email in emails {
self.contactCache[email] = completeContact
}
// Also index by contact identifier for direct lookup
self.contactCache[contact.identifier] = completeContact
}
NSLog("iMessageReader: Loaded \(contactCache.count) contact entries")
@ -140,12 +144,14 @@ class iMessageReader {
}
/// Get all cached contacts for syncing
/// Each CNContact from address book is already a single person - deduplicate by identifier
func getAllContacts() -> [ContactInfo] {
// Deduplicate by display name (same person may have multiple phones/emails)
// Deduplicate by identifier (unique per CNContact, not by name)
// This prevents merging two different "John Smith" contacts
var seen = Set<String>()
return contactCache.values.filter { contact in
if seen.contains(contact.displayName) { return false }
seen.insert(contact.displayName)
if seen.contains(contact.identifier) { return false }
seen.insert(contact.identifier)
return true
}
}