I store my contacts in an org file called people.org. The file has the following structure:
* John Doe :PROPERTIES: :ID: some-generated-uuid :GROUP: Work :PHONE: +1234567890 :ADDRESS_HOME: Foo bar street, no 5 :EMAIL: john@doe.com :END: * Dohn Joe :PROPERTIES: :GROUP: High school :PHONE: +1334567890 :END: - Some notes about this person.
The nice part is, it's just plain org-mode. I only use top-level headings in this file, instead of creating header hierarchies. I utilize :GROUP: property to categorize people, this way a person may belong to multiple categories. I use org-ql if I need to find people related to one group or if I want to filter them based on some specific property.
However the main use case is that I reference these headers in my other org files. For example, I also keep my diary in org-mode and I may write about some event that I participated with John Doe from above. I simply reference (using org links) to that person. The benefit of this is being able to recollect all of your notes about a particular person using one simple search.
Anyway, lets jump how I synchronize these contact information with my phone.
I simply create .vcf file, a format that most of the contacts apps that are aware of, based on my people.org file. Then I synchronize this .vcf file to my phone, using Syncthing. The following snippet creates the .vcf file.
(defun isamert/build-contact-item (template-string contact-property)
(if-let ((stuff (org-entry-get nil contact-property)))
(concat (format template-string stuff) "\n")
""))
(defun isamert/vcard ()
"Create a .vcf file containing all contact information."
(interactive)
(write-region
(string-join
(org-map-entries
(lambda ()
(string-join
`("BEGIN:VCARD\nVERSION:2.1\n"
,(format "UID:urn:uuid:%s\n" (org-id-get nil t))
,(isamert/build-contact-item "FN:%s" "ITEM")
,(isamert/build-contact-item "TEL;CELL:%s" "PHONE")
,(isamert/build-contact-item "EMAIL:%s" "EMAIL")
,(isamert/build-contact-item "ORG:%s" "GROUP")
,(isamert/build-contact-item "ADR;HOME:;;%s" "ADDRESS_HOME")
,(isamert/build-contact-item "ADR;WORK:;;%s" "ADDRESS_WORK")
,(format "REV:%s\n" (format-time-string "%Y-%m-%dT%T"))
"END:VCARD")
""))
"LEVEL=1")
"\n")
nil
(read-file-name
"Where to save the .vcf file?"
"~/Documents/sync/"
"contacts.vcf")))
Simply call the isamert/vcard function in your people.org file and you get a .vcf file. By default, it creates the file under ~/Documents/sync. This folder is automatically synced with my phone using Syncthing. Then I open my contacts app and import the file. That's it.
I used to earliest possible .vcf format that is available so that every contacts app can import them. You can add/remove fields to your .vcf export quite easily, just take a look at this wikipedia page for vCard and the relevant line to your function.
Here is an example, just to demonstrate how you obtain/copy email of one of your contacts interactively. A use case might be:
isamert/contacts-select-email which presents you all of your contact's names.To: field of your email client.
Don't forget to point find-file-noselect to your people.org file.
(defun isamert/contacts-email-alist ()
"Get an alist of contact name and emails."
(seq-filter
(lambda (it) it)
(org-map-entries
(lambda ()
(when-let ((email (org-entry-get nil "EMAIL")))
`(,(org-entry-get nil "ITEM") . ,email)))
"LEVEL=1")))
(defun isamert/contacts-select-email ()
"Search through your contacts interactively and copy their email."
(interactive)
(with-current-buffer (find-file-noselect "~/Documents/notes/people.org")
(let ((email-alist (isamert/contacts-email-alist)))
(kill-new
(cdr
(assoc
(completing-read "Copy email of: " email-alist)
email-alist))))))
UID section to entries so that when you re-import your contacts after an update to the vcf file, already-existing contacts won't get duplicated.
Comments