Scripting Contacts

I have a script that ran perfectly with the Address Book application. Moving it to run with the Contacts application now throws an error about how it must be a ‘person’ to add to a cohort. The contact info is sucked in from a spreadsheet satisfactorily. Here is the complete code:


global Cohort
global Roster
global NA
set WasFound to false
set Roster to {}
set NA to "#N/A"

launch application "Contacts"

tell application "Microsoft Excel" to activate
tell application "Microsoft Excel" to set Cohort to string value of cell "Edit!A37"
tell application "Contacts" to activate
tell application "Contacts"
	set myGroups to (get name of every group)
	if myGroups contains Cohort then set WasFound to true
	if not WasFound then set theGroup to make new group with properties {name:Cohort}
end tell
tell application "Microsoft Excel" to activate
tell application "Microsoft Excel"
	repeat with i from 2 to 33
		if string value of cell ("Edit!B" & i) is NA then
			set j to i - 1
			exit repeat
		end if
	end repeat
end tell

repeat with i from 2 to j
	set TEMP to {key:i - 1, LastName:"", FirstName:"", MI:"", Rank:"", |Hash|:"", |Nickname|:"", Srvc:"", Corps:"", Addressline1:"", |City|:"", |State|:"", |Zip|:"", HomePhone:"", |Email|:"", ParentMilitaryCommand:"", MobilePhone:"", WorkPhone:"", EmailDomain:""}
	tell application "Microsoft Excel"
		set LastName of TEMP to string value of cell ("Edit!B" & i)
		set FirstName of TEMP to string value of cell ("Edit!C" & i)
		set MI of TEMP to string value of cell ("Edit!D" & i)
		set Rank of TEMP to string value of cell ("Edit!F" & i)
		set |Hash| of TEMP to string value of cell ("Edit!G" & i)
		set |Nickname| of TEMP to string value of cell ("Edit!H" & i)
		set Srvc of TEMP to string value of cell ("Edit!J" & i)
		set Corps of TEMP to string value of cell ("Edit!M" & i)
		if Corps of TEMP is NA then set Corps of TEMP to ""
		set Addressline1 of TEMP to string value of cell ("Edit!N" & i)
		try
			if (not cell ("Edit!O" & i) is missing value) and (string value of cell ("Edit!O" & i) ≠ "") then
				set Addressline1 of TEMP to Addressline1 of TEMP & ", " & string value of cell ("Edit!O" & i)
				--AddressLine2
			end if
		end try
		set |City| of TEMP to string value of cell ("Edit!P" & i)
		set |State| of TEMP to string value of cell ("Edit!Q" & i)
		set |Zip| of TEMP to string value of cell ("Edit!R" & i)
		set HomePhone of TEMP to string value of cell ("Edit!S" & i)
		set |Email| of TEMP to string value of cell ("Edit!T" & i)
		set ParentMilitaryCommand of TEMP to string value of cell ("Edit!U" & i)
		set MobilePhone of TEMP to string value of cell ("Edit!V" & i)
		set WorkPhone of TEMP to string value of cell ("Edit!W" & i)
		set EmailDomain of TEMP to my rightstr(|Email| of TEMP, 4)
	end tell
	tell application "Contacts"
		set theStudent to make new person
		set last name of theStudent to LastName of TEMP
		set first name of theStudent to FirstName of TEMP
		set middle name of theStudent to MI of TEMP
		set title of theStudent to Rank of TEMP
		set note of theStudent to "Cohort: " & Cohort & linefeed & "Hash:   " & |Hash| of TEMP
		set nickname of theStudent to |Nickname| of TEMP
		set suffix of theStudent to Srvc of TEMP
		set job title of theStudent to Corps of TEMP
		set organization of theStudent to ParentMilitaryCommand of TEMP
		if EmailDomain of TEMP is ".mil" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:|Email| of TEMP}
		else if EmailDomain of TEMP is ".gov" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:|Email| of TEMP}
		else
			make new email at end of every email of theStudent with properties ¬
				{label:"Home", value:|Email| of TEMP}
		end if
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Work", value:WorkPhone of TEMP}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Home", value:HomePhone of TEMP}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Mobile", value:MobilePhone of TEMP}
		make new address at end of every address of theStudent with properties ¬
			{label:"Home", street:Addressline1 of TEMP, city:|City| of TEMP, state:|State| of TEMP, zip:|Zip| of TEMP}
		set cls to class of theStudent
		add theStudent to group Cohort
		save
	end tell
	set the end of Roster to theStudent
end repeat
beep

on rightstr(txt, N)
	set C to length of txt
	if N > C then set N to C
	set rtntxt to text (C - N + 1) thru C of txt
	return rtntxt
end rightstr

You’ll note that I verify the class of thestudent just before the add step. It is a person, yet the error is, “Contacts got an error: You can only add a person to a group.” Yeah, OK, but?

I’m clueless about the Contacts error. All help appreciated. Thanks

As first step, replace this code:

tell application "Contacts" to activate
tell application "Contacts"
   set myGroups to (get name of every group)
   if myGroups contains Cohort then set WasFound to true
   if not WasFound then set theGroup to make new group with properties {name:Cohort}
end tell

WITH THIS:

tell application "Contacts"
	activate
	if not (group Cohort exists) then make new group with properties {name:Cohort}
	save
end tell

Then, as boolean variable WasFound no need, remove code line set WasFound to false.

I can’t see any reason why your Contacts code shouldn’t work. Simply making a person and adding it to a group works with Contacts 12.0 in Mojave and there’s nothing in your code to change the values of theStudent or Cohort to anything other than what you think they are. :confused:

A couple of things I’d try — just in desperation to see if they made any difference — would be to create the person in situ in the group rather than adding it later …

tell application "Contacts"
	set theStudent to (make new person at group Cohort with properties {last name:(LastName of TEMP), first name:(FirstName of TEMP)})
	-- etc.
end tell

… and moving the ‘save’ command to after the repeat so that it’s only executed once after all the person creation is done.

Hi, Nigel.

I checked - his checking for the existence of the group did not create the group in case of its absence. Now I have no time to go into subtleties, but with my code it was created.

In addition, I see a lot of extra code here. Is it really possible to do without a template (TMP) and set the person’s properties directly from Excel?

I go to work.

Hi KniazidisR.

Your code’s more concise, but the original does also create the group if it doesn’t exist. It’s your ‘save’ command which makes any newly created group visible in Contacts. It may be a good idea to include this anyway after creating a group, although I wouldn’t bother after not doing so.

tell application "Contacts"
	activate
	if not (group Cohort exists) then
		make new group with properties {name:Cohort}
		save
	end if
end tell

But if the problem were the group not existing when the person was added, the error message would be “Can’t get group "Blah".” — taking “Blah” as the value of Cohort. :wink:

Possibly. I don’t know anything about scripting Excel. The immediate problem’s to work out why the Contacts code’s not working on Mick536’s system. I haven’t been able to reproduce the error message yet. :confused:

Hi Nigel – Thank you for your help. Working on it tonight.

System is High Sierra, running Contacts 11.0, and Script Debugger 7.0.8.

–Mick536

Hello All –

I believe I have made all recommended changes, as well as wrapping the email, phone, and address properties in parens per Nigel’s suggestion. Code is now:


global Cohort
global Roster
global NA
set Roster to {}
set NA to "#N/A"

launch application "Contacts"

tell application "Microsoft Excel"
	activate
	set Cohort to string value of cell "Edit!A37"
end tell
tell application "Contacts"
	activate
	if not (group Cohort exists) then make new group with properties {name:Cohort}
	save
end tell

tell application "Microsoft Excel"
	activate
	repeat with i from 2 to 33
		if string value of cell ("Edit!B" & i) is NA then
			set j to i - 1
			exit repeat
		end if
	end repeat
end tell

repeat with i from 2 to j
	set TEMP to {key:i - 1, LastName:"", FirstName:"", MI:"", Rank:"", |Hash|:"", |Nickname|:"", Srvc:"", Corps:"", Addressline1:"", |City|:"", |State|:"", |Zip|:"", HomePhone:"", |Email|:"", ParentMilitaryCommand:"", MobilePhone:"", WorkPhone:"", EmailDomain:""}
	tell application "Microsoft Excel"
		set LastName of TEMP to string value of cell ("Edit!B" & i)
		set FirstName of TEMP to string value of cell ("Edit!C" & i)
		set MI of TEMP to string value of cell ("Edit!D" & i)
		set Rank of TEMP to string value of cell ("Edit!F" & i)
		set |Hash| of TEMP to string value of cell ("Edit!G" & i)
		set |Nickname| of TEMP to string value of cell ("Edit!H" & i)
		set Srvc of TEMP to string value of cell ("Edit!J" & i)
		set Corps of TEMP to string value of cell ("Edit!M" & i)
		if Corps of TEMP is NA then set Corps of TEMP to ""
		set Addressline1 of TEMP to string value of cell ("Edit!N" & i)
		try
			if (not cell ("Edit!O" & i) is missing value) and (string value of cell ("Edit!O" & i) ≠ "") then
				set Addressline1 of TEMP to Addressline1 of TEMP & ", " & string value of cell ("Edit!O" & i)
				--AddressLine2
			end if
		end try
		set |City| of TEMP to string value of cell ("Edit!P" & i)
		set |State| of TEMP to string value of cell ("Edit!Q" & i)
		set |Zip| of TEMP to string value of cell ("Edit!R" & i)
		set HomePhone of TEMP to string value of cell ("Edit!S" & i)
		set |Email| of TEMP to string value of cell ("Edit!T" & i)
		set ParentMilitaryCommand of TEMP to string value of cell ("Edit!U" & i)
		set MobilePhone of TEMP to string value of cell ("Edit!V" & i)
		set WorkPhone of TEMP to string value of cell ("Edit!W" & i)
		set EmailDomain of TEMP to my rightstr(|Email| of TEMP, 4)
	end tell
	tell application "Contacts"
		set theStudent to make new person with properties ¬
			{last name:(LastName of TEMP), first name:(FirstName of TEMP), middle name:(MI of TEMP), title:(Rank of TEMP), note:("Cohort: " & Cohort & linefeed & "Hash:   " & |Hash| of TEMP), nickname:(|Nickname| of TEMP), suffix:(Srvc of TEMP), job title:(Corps of TEMP), organization:(ParentMilitaryCommand of TEMP)}
		if EmailDomain of TEMP is ".mil" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:(|Email| of TEMP)}
		else if EmailDomain of TEMP is ".gov" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:(|Email| of TEMP)}
		else
			make new email at end of every email of theStudent with properties ¬
				{label:"Home", value:(|Email| of TEMP)}
		end if
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Work", value:(WorkPhone of TEMP)}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Home", value:(HomePhone of TEMP)}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Mobile", value:(MobilePhone of TEMP)}
		make new address at end of every address of theStudent with properties ¬
			{label:"Home", street:(Addressline1 of TEMP), city:(|City| of TEMP), state:(|State| of TEMP), zip:(|Zip| of TEMP)}
	
		set cls to class of theStudent
		add theStudent to group Cohort
	end tell
	set the end of Roster to theStudent
end repeat
save
beep

on rightstr(txt, N)
	set C to length of txt
	if N > C then set N to C
	set rtntxt to text (C - N + 1) thru C of txt
	return rtntxt
end rightstr

Unfortunately, we have the same error. TheStudent id looks like this: “7192B836-A9B8-4FE8-AC41-147B0995959A:ABPerson” I find -10002 errors in all the email, phone, and address properties. They look like this for the address: ‘country of address “home” of theStudent error getting country code -10002’ as per Script Debugger’s variable window for example. -10002 is “Invalid key form was detected.”

So I feel the problem is there in the ‘Make new’ syntax. Just don’t know what it is. All values are properly set in TEMP.

Thanks for any help.

…Mick536

Model: MacBook Pro 17" late 2011
Browser: Firefox 69.0
Operating System: macOS 10.13

Of course, it’s better to put the save command outside the repeat loop, but it should remain inside the tell application “Contacts” block. That is, this piece of code:

set cls to class of theStudent
       add theStudent to group Cohort
   end tell
   set the end of Roster to theStudent
end repeat
save
beep

should be like this:

set cls to class of theStudent
       add theStudent to group Cohort
   set the end of Roster to theStudent
end repeat
save
end tell
beep

Check your phone numbers in Excel document too. List of valid International Calling Codes you can find HERE

This Excel-free test version of the script works perfectly for me, both when creating the person in the group and when adding it after creation. (Contacts 12.0, macOS 10.14.6 (supplementary update), Script Editor and Script Debugger.) If it works for Mick536 too, it would suggest something wrong with the values coming out of Excel. What does TEMP look like after its values are set?

If this script doesn’t work for Mick536, it would suggest some problem with Contacts. :confused: But first try quitting Contacts to clear any unsaved work from previous script runs.

set Cohort to "Cohort Test" -- Group name. Change if required.

tell application "Contacts"
	activate
	if not (group Cohort exists) then
		make new group with properties {name:Cohort}
		save
	end if
end tell

set j to 10
repeat with i from 2 to j
	set TEMP to {key:i - 1, LastName:"Bloggs", FirstName:"Bill", MI:"Bertie", Rank:"", |Hash|:"Huh?", |Nickname|:"Fishface", Srvc:"BSA & handlebar", Corps:"Sergeant", Addressline1:"3 Railway Cuttings", |City|:"Coventry", |State|:"", |Zip|:"CV1 1AB", HomePhone:"01234 70526", |Email|:"", ParentMilitaryCommand:"Royal Arquebusiers", MobilePhone:"07569 406789", WorkPhone:"", EmailDomain:""}
	set |Email| of TEMP to some item of {"fface@gmail.mil", "bbbloggs@gmail.gov", "billb99@gmail.com"}
	set EmailDomain of TEMP to my rightstr(|Email| of TEMP, 4)
	
	tell application "Contacts"
		-- Create the person in the group.
		set theStudent to make new person at group Cohort with properties ¬
			{last name:(LastName of TEMP), first name:(FirstName of TEMP), middle name:(MI of TEMP), title:(Rank of TEMP), note:("Cohort: " & Cohort & linefeed & "Hash: " & |Hash| of TEMP), nickname:(|Nickname| of TEMP), suffix:(Srvc of TEMP), job title:(Corps of TEMP), organization:(ParentMilitaryCommand of TEMP)}
		(* -- Or create the person to add to the group later.
		set theStudent to make new person with properties ¬
		{last name:(LastName of TEMP), first name:(FirstName of TEMP), middle name:(MI of TEMP), title:(Rank of TEMP), note:("Cohort: " & Cohort & linefeed & "Hash: " & |Hash| of TEMP), nickname:(|Nickname| of TEMP), suffix:(Srvc of TEMP), job title:(Corps of TEMP), organization:(ParentMilitaryCommand of TEMP)} *)
		if EmailDomain of TEMP is ".mil" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:(|Email| of TEMP)}
		else if EmailDomain of TEMP is ".gov" then
			make new email at end of every email of theStudent with properties ¬
				{label:"Work", value:(|Email| of TEMP)}
		else
			make new email at end of every email of theStudent with properties ¬
				{label:"Home", value:(|Email| of TEMP)}
		end if
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Work", value:(WorkPhone of TEMP)}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Home", value:(HomePhone of TEMP)}
		make new phone at end of every phone of theStudent with properties ¬
			{label:"Mobile", value:(MobilePhone of TEMP)}
		make new address at end of every address of theStudent with properties ¬
			{label:"Home", street:(Addressline1 of TEMP), city:(|City| of TEMP), state:(|State| of TEMP), zip:(|Zip| of TEMP)}
		
		set cls to class of theStudent
		-- add theStudent to group Cohort
	end tell
end repeat
tell application "Contacts" to save

on rightstr(txt, N)
	set C to length of txt
	if N > C then set N to C
	return text -N thru C of txt
end rightstr

Hello Nigel, R —

Thank you for your help. (R: I never get that far, but I’ve made a change. :|)

Good news and bad news. The good news is that Nigel’s test ran (except to iCloud instead of ‘on my Mac’. I’d like to know how to specify that.) The bad news is that I booted off a system 10.6 external drive, and my original script ran perfectly to the ‘Address Book’ on that disk using the exact same spreadsheet (it’s on a common 3rd drive.) The good news is that Script Debugger 5 reported the same errors, but the Address Book doesn’t care. The bad news is that Contacts does. Oh, and I’ve got 18 ‘Fishface’ Bloggs in my Contacts. :slight_smile:

When I get a moment, I’ll comment out all the address, phone, and email lines, and report back.

Thanks again.
…Mick

Hello, All —

Nigel’s code created a group “Cohort Test” on iCloud for me. I changed only line 60 above to


set theStudent to make new person at group "Cohort Test" with properties ...

and I populated “Cohort Test” with my 20 students! No changes to Excel of any sort. So I deleted the group associated with the variable Cohort from ‘On My Mac’ and ran the script with the original line 60. And Ta Da! I now have my cohort on the cloud with my students, more or less how it used to be. All errors were meaningless to Contacts, too.

So how do I specify where to look to see if the group exists ‘On My Mac’ and not the default of the cloud?

Thanks.
…Mick

Contacts’s scripting dictionary doesn’t mention accounts. If you run this script while your iCloud and On My Mac accounts are both available …

tell application "Contacts"
	name of groups
end tell

… do the names of the groups come from both accounts or just from one of them?

Do you want the groups to be created in a particular account?

It looks as if it may be possible to do a few accounty things using the “Contacts” framework in ASObjC rather than the Contacts application — or perhaps using a bit of both. But I’d need to do some more research and only have an On My Mac account with which to play. What does this script return on your machine while you’re on line?

use AppleScript version "2.5" -- Mac OS 10.11 (El Capitan) or later.
use framework "Foundation"
use framework "Contacts"

set contactStore to current application's class "CNContactStore"'s new()
set allContainers to contactStore's containersMatchingPredicate:(missing value) |error|:(missing value)

return (allContainers's valueForKey:("identifier")) as list

Hopefully the result will be a two-item list with one of the items being “_local:ABAccount” and the other being the ID of your iCloud account. There’s no need to post the actual result if it gives away anything about your iCloud account. I just need to know if its definitely possible to distinguish between the accounts this way.

Hi Nigel —

Scriplet #1: From both iCloud and ‘On My Mac’

Scriplet #2: Returned 3 items. I have a Google account, too. As follows:
item 1 of result
“(account ID):ABAccount”
item 2 of result
“(account ID):ABAccount”
item 3 of result
“_local:ABAccount”

The Google account has no groups. I have the account for internet purposes, but I don’t even have a gmail account. Don’t know how it got there. Don’t know which of item 1 or item 2 it is.

This is high level Applescript sorcery. Thank you.

…best, Mick

Hi Mick.

Thanks. That’s helpful. I’m experimenting this morning. Meanwhile, if you change “identifier” to “name” in my ASObjC script above, do the names other than “On My Mac” make it easier to identify the accounts?

G’morning, Nigel —

Well, it’s morning my time, anyway. Thank you.
item 1 of result: “Google”
item 2 of result: “iCloud”
item 3 of result: “On My Mac”

So, yes, yes they do. :slight_smile:

…best, Mick

Great. Here’s what I’ve got so far. I’ve only been able to test it with my On My Mac account, but in theory it should work with any account which supports groups. At the moment, it asks you to choose the account you want to use, but this can be hard-coded in if you prefer. The setGroup() handler at the bottom creates the group in the required account if there isn’t already one there with the given name and returns the ID of the created or existing group. Referring to the group by its ID rather than by its name should be enough for Contacts to identify it correctly.

It’s possible to create and place the ‘people’ too by scripting the “Contacts” framework, but (for the moment at least) I’ve left it at creating and/or identifying the correct group.

use AppleScript version "2.5" -- Mac OS 10.11 (El Capitan) or later.
use framework "Foundation"
use framework "Contacts"
use scripting additions

property preferredAccount : "On My Mac"

on main()
	set roster to {}
	set NA to "#N/A"
	
	launch application "Contacts"
	
	tell application "Microsoft Excel"
		activate
		set Cohort to string value of cell "Edit!A37"
	end tell
	
	set CohortGroupID to setGroup(Cohort)
	
	tell application "Microsoft Excel"
		repeat with i from 2 to 33
			if string value of cell ("Edit!B" & i) is NA then
				set j to i - 1
				exit repeat
			end if
		end repeat
	end tell
	
	repeat with i from 2 to j
		set TEMP to {key:i - 1, LastName:"", FirstName:"", MI:"", Rank:"", |Hash|:"", |Nickname|:"", Srvc:"", Corps:"", Addressline1:"", |City|:"", |State|:"", |Zip|:"", HomePhone:"", |Email|:"", ParentMilitaryCommand:"", MobilePhone:"", WorkPhone:"", EmailDomain:""}
		tell application "Microsoft Excel"
			set LastName of TEMP to string value of cell ("Edit!B" & i)
			set FirstName of TEMP to string value of cell ("Edit!C" & i)
			set MI of TEMP to string value of cell ("Edit!D" & i)
			set Rank of TEMP to string value of cell ("Edit!F" & i)
			set |Hash| of TEMP to string value of cell ("Edit!G" & i)
			set |Nickname| of TEMP to string value of cell ("Edit!H" & i)
			set Srvc of TEMP to string value of cell ("Edit!J" & i)
			set Corps of TEMP to string value of cell ("Edit!M" & i)
			if Corps of TEMP is NA then set Corps of TEMP to ""
			set Addressline1 of TEMP to string value of cell ("Edit!N" & i)
			try
				if (not cell ("Edit!O" & i) is missing value) and (string value of cell ("Edit!O" & i) ≠ "") then
					set Addressline1 of TEMP to Addressline1 of TEMP & ", " & string value of cell ("Edit!O" & i)
					--AddressLine2
				end if
			end try
			set |City| of TEMP to string value of cell ("Edit!P" & i)
			set |State| of TEMP to string value of cell ("Edit!Q" & i)
			set |Zip| of TEMP to string value of cell ("Edit!R" & i)
			set HomePhone of TEMP to string value of cell ("Edit!S" & i)
			set |Email| of TEMP to string value of cell ("Edit!T" & i)
			set ParentMilitaryCommand of TEMP to string value of cell ("Edit!U" & i)
			set MobilePhone of TEMP to string value of cell ("Edit!V" & i)
			set WorkPhone of TEMP to string value of cell ("Edit!W" & i)
		end tell
		set EmailDomain of TEMP to rightStr(|Email| of TEMP, 4)
		tell application "Contacts"
			set theStudent to make new person with properties ¬
				{last name:(LastName of TEMP), first name:(FirstName of TEMP), middle name:(MI of TEMP), title:(Rank of TEMP), note:("Cohort: " & Cohort & linefeed & "Hash:   " & |Hash| of TEMP), nickname:(|Nickname| of TEMP), suffix:(Srvc of TEMP), job title:(Corps of TEMP), organization:(ParentMilitaryCommand of TEMP)}
			if EmailDomain of TEMP is ".mil" then
				make new email at end of every email of theStudent with properties ¬
					{label:"Work", value:(|Email| of TEMP)}
			else if EmailDomain of TEMP is ".gov" then
				make new email at end of every email of theStudent with properties ¬
					{label:"Work", value:(|Email| of TEMP)}
			else
				make new email at end of every email of theStudent with properties ¬
					{label:"Home", value:(|Email| of TEMP)}
			end if
			make new phone at end of every phone of theStudent with properties ¬
				{label:"Work", value:(WorkPhone of TEMP)}
			make new phone at end of every phone of theStudent with properties ¬
				{label:"Home", value:(HomePhone of TEMP)}
			make new phone at end of every phone of theStudent with properties ¬
				{label:"Mobile", value:(MobilePhone of TEMP)}
			make new address at end of every address of theStudent with properties ¬
				{label:"Home", street:(Addressline1 of TEMP), city:(|City| of TEMP), state:(|State| of TEMP), zip:(|Zip| of TEMP)}
			
			set cls to class of theStudent
			add theStudent to group id CohortGroupID
		end tell
		set the end of roster to theStudent
	end repeat
	tell application "Contacts" to save
	beep
end main

on rightStr(txt, N)
	set C to length of txt
	if N > C then set N to C
	set rtntxt to text -N thru C of txt
	return rtntxt
end rightStr

on setGroup(Cohort)
	set |⌘| to current application
	
	set contactStore to |⌘|'s class "CNContactStore"'s new()
	-- Ask the user to choose a Contacts account. A container doesn't exactly correspond to an account, but they're close enough for the current purposes.
	set allContainers to contactStore's containersMatchingPredicate:(missing value) |error|:(missing value)
	set containerNames to (allContainers's valueForKey:("name")) as list
	set containerChoice to (choose from list containerNames with prompt "Which Contacts account?" default items {preferredAccount})
	if (containerChoice is false) then error number -128
	
	-- Get the ID of the container with the chosen name.
	set containerFilter to |⌘|'s class "NSPredicate"'s predicateWithFormat:("name == %@") argumentArray:(containerChoice)
	set containerID to (allContainers's filteredArrayUsingPredicate:(containerFilter))'s firstObject()'s identifier()
	
	-- Get the names of the existing groups in this container.
	set groupFilter to |⌘|'s class "CNGroup"'s predicateForGroupsInContainerWithIdentifier:(containerID)
	set groupsInThisContainer to contactStore's groupsMatchingPredicate:(groupFilter) |error|:(missing value)
	set groupNames to groupsInThisContainer's valueForKey:("name")
	
	-- If the name passed to this handler is amongst them, get the corresponding group. Otherwise create a new one.
	-- NB. DUPLICATE NAMES ARE ALLOWED IN CONTACTS. IF THE CONTAINER CONTAINS MORE THAN ONE GROUP WITH THE SAME NAME, THIS MAY NOT RETURN THE CORRECT ONE.
	if (groupNames's containsObject:(Cohort)) then
		-- Get the (first) existing group with the given name.
		set groupFilter to |⌘|'s class "NSPredicate"'s predicateWithFormat_("name == %@", Cohort)
		set targetGroup to (groupsInThisContainer's filteredArrayUsingPredicate:(groupFilter))'s firstObject()
	else
		-- Create a new one.
		set targetGroup to |⌘|'s class "CNMutableGroup"'s new()
		tell targetGroup to setName:(Cohort)
		-- Set up a request to save it in the container chosen above.
		set saveRequest to |⌘|'s class "CNSaveRequest"'s new()
		tell saveRequest to addGroup:(targetGroup) toContainerWithIdentifier:(containerID)
		-- Execute the request. The new group appears in the Contacts application instantly (on my machine) without having to quit and reopen it.
		tell contactStore to executeSaveRequest:(saveRequest) |error|:(missing value)
	end if
	
	-- Return the group's ID. This should be enough for the Contacts application.
	return (targetGroup's identifier()) as text
end setGroup

Hi Nigel —

Thanks. Nitnoid typo, line 10. NS should be NA

The script creates the group at all 3 sites successfully, but only populates the one on iCloud. At Google and ‘On My Mac’, both fail at the same spot as way before: line 82 ‘add theStudent to group id CohortGroupID’ complaining that the student must be a person, which we verify on line 81.

Thinking that maybe multiple groups on multiple sites with the same name cause confusion, I deleted all of them and tried again. Same as above.

If it might make a difference, I commented out the main() wrappers. I don’t know how to see main()'s variables in Script Debugger otherwise when I ‘run main()’.

Obviously, you love a challenge. But I do feel that I may be monopolising < :slight_smile: your time.

…best, Mick

Ah yes. Sorry. I can get “NS” on the brain when writing ASObjCode. :rolleyes: Now corrected.

Hmm. :confused: So it looks as if the ASObjC code works, but the original problem remains: Contacts isn’t able to do anything about the account — even though it can see the target group (your report: “Scriplet #1: From both iCloud and ‘On My Mac’”) and is unequivocably using its ID (the point of my code returning it).

The error message you quoted in post #1 was “Contacts got an error: You can only add a person to a group.” You’ve been taking this to be a complaint that theStudent isn’t a person, but it seems ever more likely that the problem’s actually to do with adding to the group, ie. “You can’t add a person to anything other than a group.” Contacts can see the group — otherwise it wouldn’t return its name in response to my earlier test script and the error would be “Can’t get group ….” It’s just not recognising it as something that can be added to. The reason may something to do with the way Contacts is set up not to have accounts (or containers) in its AppleScript implementation.

I put the main code in its own handler because I like to keep variables local — and therefore non-persistent — unless there’s a good reason for doing otherwise. I couldn’t see a reason to have globals or other persistent variables in your original code. The property in my version is a behaviour-influencing variable conveniently placed for editing near the top of the script. :slight_smile:

I’ll think further about the adding problem. The research is interesting and educational even if the problem doesn’t finally get licked. :slight_smile:

Hi Nigel —

I’m evaluating if what I want is what I need. I’ll live with it for a while and see. What I certainly don’t want is to have to manually add 20+ records into Contacts with every class, and we’ve achieved that.

And there is a work-around that’s not tedious: Export the vCards from iCloud and import them ‘On My Mac.’

A last, simple question. Has the requirement to preface rightstr() with my (as in ‘my rightstr()’) been relieved? I use it and noted that you don’t, which might just be my answer. :slight_smile:

Anyway, much thanks for the abundant shared knowledge.

…best, Mick

Hi Mick.

The ‘my’ was only needed because the call to the handler was inside the Microsoft Excel ‘tell’ statement. I moved the call to one line later — outside the ‘tell’ statement, where the handler belongs to ‘me’ (the script) anyway. I also simplified the handler itself slightly. :slight_smile: