Just to be clear to any one else who may experience this issue, I did not change the name of the log file after it was created by the script. I changed both the destination (I don’t use Dropbox) and the log file name in the script itself.
Further to my observations in post #20, it doesn’t look good for getting notes from contacts when using the Contacts framework. It needs some sort of authorisation from Apple and, as far as I can see, this authorisation can only be applied to applications built with Xcode.
Better news is that vCard text obtained through the Contacts application’s AppleScript interface does still contain note information as at macOS 14.3.1 (Sonoma). So here’s another version of the script which reverts to that method:
use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions
property vPath : "~/Dropbox/Backups/vCards/"
property logPath : "~/Desktop/vCards_sdraCv.txt"
-- Replacement for slashes and/or colons in contact names when deriving file names.
property slashSubstitute : "-" -- or "_⊕⊖⊗⊘_" for Keita.
main()
on main()
script o
property allNames : missing value
property allVCards : missing value
property usedNames : {}
property duplicatedNamesLog : {}
end script
tell application "Contacts"
activate
set {o's allNames, o's allVCards} to people's {name, vcard}
quit
end tell
set expandedVPath to (current application's class "NSString"'s stringWithString:(vPath))'s stringByExpandingTildeInPath()
tell current application's class "NSFileManager"'s defaultManager() to createDirectoryAtPath:(expandedVPath) withIntermediateDirectories:(true) attributes:(missing value) |error|:(missing value)
set vHFSPath to expandedVPath as text as POSIX file as text
repeat with i from 1 to (count o's allNames)
set contactName to o's allNames's item i
if ((contactName contains "/") or (contactName contains ":")) then set contactName to replaceText(contactName, {"/", ":"}, slashSubstitute)
if (o's usedNames contains contactName) then
set duplicatedName to contactName
set contactName to contactName & ("_" & i)
set o's duplicatedNamesLog's end to duplicatedName & (" -> " & contactName & ".vcf")
else
set o's usedNames's end to contactName
end if
set vCardData to o's allVCards's item i
writeToFile(vCardData, vHFSPath & (contactName & ".vcf"), 1, «class utf8», true)
end repeat
if (o's duplicatedNamesLog is not {}) then
set duplicatedNamesLog to join(o's duplicatedNamesLog, linefeed)
set expandedLogPath to (current application's class "NSString"'s stringWithString:(logPath))'s stringByExpandingTildeInPath()
writeToFile(duplicatedNamesLog, expandedLogPath as text as POSIX file as text, 1, «class utf8», true)
set {button returned:buttonReturned} to (display alert "PLEASE NOTE" message "One or more of your contacts' names were duplicates and their vCards were saved under modified names. A log of the modified names has been saved to the file \"" & expandedLogPath's lastPathComponent() & "\" on your desktop." as critical buttons {"Open the log file", "OK"} default button "OK")
if (buttonReturned is "Open the log file") then
set logFile to expandedLogPath as text as POSIX file
tell application "Finder" to open logFile
end if
end if
end main
on join(lst, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set txt to lst as text
set AppleScript's text item delimiters to astid
return txt
end join
on replaceText(mainText, searchText, replacementText)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to searchText
set textItems to mainText's text items
set AppleScript's text item delimiters to replacementText
set mainText to textItems as text
set AppleScript's text item delimiters to astid
return mainText
end replaceText
on writeToFile(what, HFSPath, pos, encoding, replacingAllContent)
set fRef to (open for access (HFSPath as «class furl») with write permission)
if (replacingAllContent) then
set eof fRef to 0
set pos to 1
end if
try
if ((what's class is text) and (encoding is not missing value)) then
write what to fRef starting at pos as encoding
else
write what to fRef starting at pos
end if
set fileLength to (get eof fRef)
close access fRef
on error errMsg
set fileLength to (get eof fRef)
close access fRef
display alert "Error writing to file" message errMsg buttons {"OK"} default button 1
end try
return fileLength
end writeToFile
And yes, after I saved my version of the script in the Scripteditor as an application, the error message that a property is missing also appeared when I ran it. And together with your statements in post #20, I have now suddenly realised what the error means.
So a victory point for the Script Debugger! Why it runs as a script in the Scripteditor without errors is strange, but ultimately it doesn’t matter.
During my research on the net, I had even read that NSContactsUsageDescription must be added to the Info.plist … which I have now also tried. But as you already wrote, it doesn’t work that way and only terminates the application.
And thanks of course for adding the colon to the slash (which I had overlooked until now)!
In fact, Contact app’s Applescript can not only save notes and images, but also export contact cards with slashes in their names without replacing them.
Funnily enough, both slashes and colons are replaced by “-” when you export them manually. So your replacement of colons and slashes makes sense and is the safer way!
Your script runs really fast. Wow!
I, on the other hand, will need a bit longer to fully understand it… *gg
It’s a pity that it doesn’t work with the Contacts framework. But no matter: the main thing is that it works.
Thank you very much for your effort and research!
I really appreciate it.
I had to smile about “GMT”. In fact, I’m only an hour away…
Keita
Seems you are the “Contacts” expert, so here’s my question. After finding this thread to export vCards, I’d like to know if there’s an AppleScript that automates the export of a Contacts archive (*.abbu file). I know I can just open contacts and do an export, but is there a way to do this via an AppleScript? Thanks for any help.
The easiest way to do this would be with a shell script. With pure Applescript it would be more code.
Split into three parts here:
set abbuPath to do shell script "currDate=$(date \"+%Y.%m.%d\"); echo \"$HOME/Desktop/Contacts - $currDate.abbu\""
do shell script "mkdir -p " & quoted form of abbuPath
do shell script "cp -pR ~/Library/Application\\ Support/AddressBook/ " & quoted form of abbuPath
If your contacts are synchronised with iCloud: Please note that the timestamps of the backed up contacts are of course always older than those of the newer iCloud contacts!
If you try to replace the contacts with those from the abbu archive, they will immediately be re-replaced by the newer iCloud contacts.
So you would have to delete all iCloud contacts first…
I’ve modified the writeToFile() handler and the calls to it in the latest script so that the files have any existing content cleared before they’re written to. This is in case they’re overwritten with shorter content on a later occasion. It ensures that the end of the earlier longer content doesn’t remain in the file. It’s something I should have remembered to do yesterday. I may revamp the handler to look more elegant if I get a moment.
Thank you, that works incredibly well. I was able to change the date format to what I normally use and the destination directory to where I store my Contacts exports.
“If you try to replace the contacts with those from the abbu archive, they will immediately be re-replaced by the newer iCloud contacts.
So you would have to delete all iCloud contacts first…”
You are of course correct, but as much as Apple does right, it seems that some things are just counterintuitive. I would think that seeing how iCloud is keeping things in sync, the dates of all the Contacts backups would be identical on all devices, but of course they’re not. My 2020 M1 MacBook Pro backup is early January of this year, and my 2014 Mac mini backup is early January of last year. And, it seems that although any changes made on either platform (or on my iPhone for that matter) are instantly in sync on all systems, the idea of deleting all contacts up on iCloud is not an attractive choice.
@Nigel_Garvey : I would love to see your even more elegant handler. I don’t need it for this exact purpose here, as I always name backups with dates, but it’s great and I’ll definitely use it!
@Homer712 : Of course, deleting all contacts in iCloud is definitely not an attractive choice!