Here you go then, @Blue993. This version also zaps any instances of “X-SHARED-PHOTO-DISPLAY-PREF:ALWAYS_ASK” at the ends of URLs, notes, and any fields in postal addresses. Like the previous versions, it only works in Script Editor (not in Script Debugger or Script Menu) — now for the additional reason that only applications which have a special permission from Apple are allowed to access contacts’ note
properties. I don’t know the reason for this, but Script Editor appears to be one such application as at macOS 13.7.3 and macOS 15.3.
use AppleScript version "2.5" -- OS X 10.11 (El Capitan) or later
use framework "Foundation"
use framework "Contacts"
main()
on main()
set search_string to "X-SHARED-PHOTO-DISPLAY-PREF:ALWAYS_ASK"
tell application "Contacts" to if (running) then quit
set |⌘| to current application
-- Fetch all the contacts from the user's Contacts database, including their phone numbers, e-mail addresses, URL addresses, postal addresses, and notes.
set contactStore to |⌘|'s class "CNContactStore"'s new()
set contactArray to contactStore's unifiedContactsMatchingPredicate:(missing value) ¬
keysToFetch:({|⌘|'s CNContactPhoneNumbersKey, |⌘|'s CNContactEmailAddressesKey, |⌘|'s CNContactUrlAddressesKey, |⌘|'s CNContactPostalAddressesKey, |⌘|'s CNContactNoteKey}) |error|:(missing value)
set postalAddressFormatter to |⌘|'s class "CNPostalAddressFormatter"'s new()
set postalAddressKeys to {|⌘|'s CNPostalAddressCountryKey, |⌘|'s CNPostalAddressISOCountryCodeKey, |⌘|'s CNPostalAddressPostalCodeKey, |⌘|'s CNPostalAddressStateKey, |⌘|'s CNPostalAddressCityKey, |⌘|'s CNPostalAddressStreetKey}
-- Work through each contact in turn.
repeat with thisContact in contactArray
set {phonesCorrected, phoneNumbers} to checkPhoneNumbers(thisContact, search_string)
set {emailsCorrected, emailAddresses} to checkURLs(thisContact, |⌘|'s CNContactEmailAddressesKey, search_string)
set {urlsCorrected, urlAddresses} to checkURLs(thisContact, |⌘|'s CNContactUrlAddressesKey, search_string)
set {addressesCorrected, postalAddresses} to checkPostalAddresses(thisContact, postalAddressFormatter, postalAddressKeys, search_string)
set {noteCorrected, note} to checkNote(thisContact, search_string)
if ((phonesCorrected) or (emailsCorrected) or (urlsCorrected) or (addressesCorrected) or (noteCorrected)) then
set mutableContact to thisContact's mutableCopy()
if (phonesCorrected) then (mutableContact's setPhoneNumbers:(phoneNumbers))
if (emailsCorrected) then (mutableContact's setEmailAddresses:(emailAddresses))
if (urlsCorrected) then (mutableContact's setUrlAddresses:(emailAddresses))
if (addressesCorrected) then (mutableContact's setPostalAddresses:(postalAddresses))
if (noteCorrected) then (mutableContact's setNote:(note))
set saveRequest to |⌘|'s class "CNSaveRequest"'s new()
(saveRequest's updateContact:(mutableContact))
(contactStore's executeSaveRequest:(saveRequest) |error|:(missing value))
end if
end repeat
end main
on checkPhoneNumbers(thisContact, search_string)
set bogey_found to false
set phoneNumbers to thisContact's phoneNumbers()'s mutableCopy()
repeat with labeledValue in phoneNumbers
set {corrected, correctedString} to checkString(labeledValue's value()'s stringValue(), search_string)
if (corrected) then
set bogey_found to true
set newPhoneNumber to (current application's class "CNPhoneNumber"'s phoneNumberWithStringValue:(correctedString))
set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(newPhoneNumber))
end if
end repeat
return {bogey_found, phoneNumbers}
end checkPhoneNumbers
on checkURLs(thisContact, theKey, search_string)
set bogey_found to false
set emailAddresses to (thisContact's valueForKey:(theKey))'s mutableCopy()
repeat with labeledValue in emailAddresses
set {corrected, correctedString} to checkString(labeledValue's value(), search_string)
if (corrected) then
set bogey_found to true
set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(correctedString))
end if
end repeat
return {bogey_found, emailAddresses}
end checkURLs
on checkPostalAddresses(thisContact, formatter, postalAddressKeys, search_string)
set |⌘| to current application
set bogey_found to false
set postalAddresses to thisContact's postalAddresses()'s mutableCopy()
repeat with labeledValue in postalAddresses
set postalAddress to labeledValue's value()
if ((formatter's stringFromPostalAddress:(postalAddress))'s containsString:(search_string)) then
set bogey_found to true
set postalAddress to postalAddress's mutableCopy()
repeat with thisKey in postalAddressKeys
set {corrected, correctedString} to checkString(postalAddress's valueForKey:(thisKey), search_string)
if (corrected) then
if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressCountryKey)) then
(postalAddress's setCountry:(correctedString))
else if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressISOCountryCodeKey)) then
(postalAddress's setISOCountryCode:(correctedString))
else if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressPostalCodeKey)) then
(postalAddress's setPostalCode:(correctedString))
else if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressStateKey)) then
(postalAddress's setState:(correctedString))
else if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressCityKey)) then
(postalAddress's setCity:(correctedString))
else if (thisKey's isEqualToString:(|⌘|'s CNPostalAddressStreetKey)) then
(postalAddress's setStreet:(correctedString))
end if
end if
end repeat
set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(postalAddress))
end if
end repeat
return {bogey_found, postalAddresses}
end checkPostalAddresses
on checkNote(thisContact, search_string)
return checkString(thisContact's note(), search_string)
end checkNote
on checkString(theString, search_string)
set bogeyRange to theString's rangeOfString:(search_string)
set bogey_found to (bogeyRange's |length|() > 0)
if (bogey_found) then set theString to theString's substringWithRange:({0, bogeyRange's location()})
return {bogey_found, theString}
end checkString
Edit: Renamed the handler which deals with both e-mail addresses and Web URLs to reduce confusion.