Help modifying a script for contacts issue via Script Editor

Hello, I’m looking to modify an existing script I have found online, source. The purpose of this script is to remove the “X-SHARED-PHOTO-DISPLAY-PREF:ALWAYS_ASK” text that was added on to hundreds of contacts after importing them from iPhone contacts to iCloud contacts.

The existing script targets the native contact list on the Mac, which works fine, but when trying to get it to target the iCloud contact list, it errors out. Can anyone please help modify the existing script?

-- Define the search string
set search_string to "X-SHARED-PHOTO-DISPLAY-PREF:ALWAYS_ASK"

-- Get the list of contacts from the Contacts app
tell application "Contacts"
	set contact_list to people
end tell

-- Iterate through each contact
repeat with contact in contact_list
	set match_found to false

	tell application "Contacts"
		-- Iterate through each phone number of the contact
		repeat with phone_number in phones of contact
			set phone_value to value of phone_number
			
			-- Check if the phone number contains the search string
			if phone_value contains search_string then
				-- Remove the search string from the phone number
				set correct_phone_value to text 1 thru ((offset of search_string in phone_value) - 1) of phone_value
				set value of phone_number to correct_phone_value
				
				set match_found to true
			end if
		end repeat
		
		-- Save the contact if a match was found
		if match_found then save contact
	end tell
end repeat

I tried modifying the “set contact_list to people” to the following below but none have worked so far.

  • “All iCloud”, All iCloud, “iCloud”, iCloud, “General”, General

Below is are images of the error I receive when I run the script.

Thank you!

Trying to target the contact list highlighted in red

Hi @pndr. Welcome to MacScripter.

Contacts.app’s scripting dictionary doesn’t offer a way to target particular accounts and I’m not sure what different accounts are included when it returns people.

I’ve put together a version of the script which targets the Contacts framework directly instead of using the Contacts application. I don’t know if it’ll be any more successful for your purposes, but it’s worth a try. It doesn’t work when run in Script Debugger, possibly because of the thread on which SD runs scripts. But it does work in Script Editor on my system and may do so in the script menu or as an application in its own right.

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
	set contactStore to |⌘|'s class "CNContactStore"'s new()
	
	-- Fetch all the contacts in the user's database (regardless of account?)
	-- We're only interested in their phone numbers.
	set contactArray to contactStore's unifiedContactsMatchingPredicate:(missing value) ¬
		keysToFetch:({|⌘|'s CNContactPhoneNumbersKey}) |error|:(missing value)
	
	repeat with thisContact in contactArray
		set match_found to false
		set phones to thisContact's phoneNumbers()'s mutableCopy() -- Editable array of labeled values.
		repeat with labeledValue in phones
			set numberString to labeledValue's value()'s stringValue()
			-- Does the search string occur in the phone number string?
			set bogeyRange to (numberString's rangeOfString:(search_string))
			-- If so, replace the labeled value in the phones list with an edited version.
			if (bogeyRange's |length|() > 0) then
				set match_found to true
				set correctedString to (numberString's substringWithRange:({0, bogeyRange's location()}))
				set newPhoneValue to (|⌘|'s class "CNPhoneNumber"'s phoneNumberWithStringValue:(correctedString))
				set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(newPhoneValue))
			end if
		end repeat
		-- If any of the labeled values were changed, save an edited copy of the contact.
		if (match_found) then
			set mutableContact to thisContact's mutableCopy()
			(mutableContact's setPhoneNumbers:(phones))
			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

Hello @Nigel_Garvey!

Thank you for the script. It worked flawlessly. It even removed the ones in the General folder I created as a test.

I didn’t realize earlier but email attributes are also affected, as shown below.

Could you please help again by updating the script to target emails instead of phone numbers?

I tried modifying it myself using the article below but was unsuccessful.

Thank you!

Hi @pndr.

Here you go. E-mail addresses, like phone numbers, are labeled values, but their values are the strings themselves instead of structures containing the strings.

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
	set contactStore to |⌘|'s class "CNContactStore"'s new()
	
	-- Fetch all the contacts in the user's database (regardless of account?)
	-- In this script, we're only interested in their e-mail addresses.
	set contactArray to contactStore's unifiedContactsMatchingPredicate:(missing value) ¬
		keysToFetch:({|⌘|'s CNContactEmailAddressesKey}) |error|:(missing value)
	
	repeat with thisContact in contactArray
		set match_found to false
		set emailAddresses to thisContact's emailAddresses()'s mutableCopy() -- Editable array of labeled values.
		repeat with labeledValue in emailAddresses
			set addressString to labeledValue's value()
			-- Does the search string occur in the e-mail address string?
			set bogeyRange to (addressString's rangeOfString:(search_string))
			-- If so, replace the labeled value in the emailAddresses list with an edited version.
			if (bogeyRange's |length|() > 0) then
				set match_found to true
				set correctedString to (addressString's substringWithRange:({0, bogeyRange's location()}))
				set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(correctedString))
			end if
		end repeat
		-- If any of the labeled values were changed, save an edited copy of the contact.
		if (match_found) then
			set mutableContact to thisContact's mutableCopy()
			(mutableContact's setEmailAddresses:(emailAddresses))
			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

It worked perfectly again. Thank you for all your help!

Here for completeness is a combination of the above two scripts which checks the phone numbers and e-mail addresses in the same run:

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 and e-mail addresses.
	set contactStore to |⌘|'s class "CNContactStore"'s new()
	set contactArray to contactStore's unifiedContactsMatchingPredicate:(missing value) ¬
		keysToFetch:({|⌘|'s CNContactPhoneNumbersKey, |⌘|'s CNContactEmailAddressesKey}) |error|:(missing value)
	-- Work through each in turn.
	repeat with thisContact in contactArray
		set {phonesCorrected, phoneNumbers} to checkPhoneNumbers(thisContact, search_string)
		set {emailsCorrected, emailAddresses} to checkEmailAddresses(thisContact, search_string)
		if ((phonesCorrected) or (emailsCorrected)) then
			set mutableContact to thisContact's mutableCopy()
			if (phonesCorrected) then (mutableContact's setPhoneNumbers:(phoneNumbers))
			if (emailsCorrected) then (mutableContact's setEmailAddresses:(emailAddresses))
			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 match_found to false
	set phoneNumbers to thisContact's phoneNumbers()'s mutableCopy()
	repeat with labeledValue in phoneNumbers
		set numberString to labeledValue's value()'s stringValue()
		set bogeyRange to (numberString's rangeOfString:(search_string))
		if (bogeyRange's |length|() > 0) then
			set match_found to true
			set correctedString to (numberString's substringWithRange:({0, bogeyRange's location()}))
			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 {match_found, phoneNumbers}
end checkPhoneNumbers

on checkEmailAddresses(thisContact, search_string)
	set match_found to false
	set emailAddresses to thisContact's emailAddresses()'s mutableCopy()
	repeat with labeledValue in emailAddresses
		set addressString to labeledValue's value()
		set bogeyRange to (addressString's rangeOfString:(search_string))
		if (bogeyRange's |length|() > 0) then
			set match_found to true
			set correctedString to (addressString's substringWithRange:({0, bogeyRange's location()}))
			set labeledValue's contents to (labeledValue's labeledValueBySettingValue:(correctedString))
		end if
	end repeat
	return {match_found, emailAddresses}
end checkEmailAddresses