contacts.swift

 1import Foundation
 2import Contacts
 3
 4// Compilation: swiftc contacts.swift -o contacts
 5// Usage: ./contacts [searchQuery]
 6
 7struct ContactEntry: Codable {
 8    let id: String
 9    let name: String
10    let emails: [String]
11    let thumbnail: String? // Base64 encoded image data
12}
13
14func fetchContacts(query: String?) {
15    let store = CNContactStore()
16    
17    store.requestAccess(for: .contacts) { (granted, error) in
18        guard granted else {
19            print("[]")
20            exit(0)
21        }
22        
23        let keys = [
24            CNContactGivenNameKey as CNKeyDescriptor,
25            CNContactFamilyNameKey as CNKeyDescriptor,
26            CNContactEmailAddressesKey as CNKeyDescriptor,
27            CNContactThumbnailImageDataKey as CNKeyDescriptor,
28            CNContactIdentifierKey as CNKeyDescriptor
29        ]
30        
31        var results: [ContactEntry] = []
32        let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
33        
34        // If a query is provided, we use a predicate to filter contacts by name
35        if let query = query, !query.isEmpty {
36            fetchRequest.predicate = CNContact.predicateForContacts(matchingName: query)
37        }
38
39        do {
40            try store.enumerateContacts(with: fetchRequest) { (contact, stop) in
41                let fullName = [contact.givenName, contact.familyName]
42                    .filter { !$0.isEmpty }
43                    .joined(separator: " ")
44                
45                let emails = contact.emailAddresses.map { $0.value as String }
46                
47                // Only include contacts that actually have an email address
48                if !emails.isEmpty {
49                    var thumbnailBase64: String? = nil
50                    if let imageData = contact.thumbnailImageData {
51                        thumbnailBase64 = imageData.base64EncodedString()
52                    }
53                    
54                    results.append(ContactEntry(
55                        id: contact.identifier,
56                        name: fullName,
57                        emails: emails,
58                        thumbnail: thumbnailBase64
59                    ))
60                }
61            }
62            
63            let encoder = JSONEncoder()
64            if let data = try? encoder.encode(results),
65               let jsonString = String(data: data, encoding: .utf8) {
66                print(jsonString)
67            }
68        } catch {
69            print("[]")
70        }
71        exit(0)
72    }
73}
74
75let args = ProcessInfo.processInfo.arguments
76let query = args.count > 1 ? args[1] : nil
77
78fetchContacts(query: query)
79RunLoop.main.run()