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()