mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
73 lines
3.0 KiB
Swift
73 lines
3.0 KiB
Swift
import Contacts
|
|
import Foundation
|
|
import OpenClawKit
|
|
|
|
final class ContactsService: ContactsServicing {
|
|
func search(params: OpenClawContactsSearchParams) async throws -> OpenClawContactsSearchPayload {
|
|
let store = CNContactStore()
|
|
let status = CNContactStore.authorizationStatus(for: .contacts)
|
|
let authorized = await Self.ensureAuthorization(store: store, status: status)
|
|
guard authorized else {
|
|
throw NSError(domain: "Contacts", code: 1, userInfo: [
|
|
NSLocalizedDescriptionKey: "CONTACTS_PERMISSION_REQUIRED: grant Contacts permission",
|
|
])
|
|
}
|
|
|
|
let limit = max(1, min(params.limit ?? 25, 200))
|
|
let keys: [CNKeyDescriptor] = [
|
|
CNContactIdentifierKey as CNKeyDescriptor,
|
|
CNContactGivenNameKey as CNKeyDescriptor,
|
|
CNContactFamilyNameKey as CNKeyDescriptor,
|
|
CNContactOrganizationNameKey as CNKeyDescriptor,
|
|
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
|
CNContactEmailAddressesKey as CNKeyDescriptor,
|
|
]
|
|
|
|
var contacts: [CNContact] = []
|
|
if let query = params.query?.trimmingCharacters(in: .whitespacesAndNewlines), !query.isEmpty {
|
|
let predicate = CNContact.predicateForContacts(matchingName: query)
|
|
contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
|
|
} else {
|
|
let request = CNContactFetchRequest(keysToFetch: keys)
|
|
try store.enumerateContacts(with: request) { contact, stop in
|
|
contacts.append(contact)
|
|
if contacts.count >= limit {
|
|
stop.pointee = true
|
|
}
|
|
}
|
|
}
|
|
|
|
let sliced = Array(contacts.prefix(limit))
|
|
let payload = sliced.map { contact in
|
|
OpenClawContactPayload(
|
|
identifier: contact.identifier,
|
|
displayName: CNContactFormatter.string(from: contact, style: .fullName)
|
|
?? "\(contact.givenName) \(contact.familyName)".trimmingCharacters(in: .whitespacesAndNewlines),
|
|
givenName: contact.givenName,
|
|
familyName: contact.familyName,
|
|
organizationName: contact.organizationName,
|
|
phoneNumbers: contact.phoneNumbers.map { $0.value.stringValue },
|
|
emails: contact.emailAddresses.map { String($0.value) })
|
|
}
|
|
|
|
return OpenClawContactsSearchPayload(contacts: payload)
|
|
}
|
|
|
|
private static func ensureAuthorization(store: CNContactStore, status: CNAuthorizationStatus) async -> Bool {
|
|
switch status {
|
|
case .authorized, .limited:
|
|
return true
|
|
case .notDetermined:
|
|
return await withCheckedContinuation { cont in
|
|
store.requestAccess(for: .contacts) { granted, _ in
|
|
cont.resume(returning: granted)
|
|
}
|
|
}
|
|
case .restricted, .denied:
|
|
return false
|
|
@unknown default:
|
|
return false
|
|
}
|
|
}
|
|
}
|