commit f17739dc9254d6b8f6ef1174b8523940623f46cb Author: Jaidyn Ann Date: Sun May 3 16:11:00 2020 -0500 Init diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..b6fa612 --- /dev/null +++ b/README.txt @@ -0,0 +1,51 @@ +VCARD2PERSON (and PERSON2VCARD) +=============================================================================== +Some bash scripts that can convert in-between vcard and Haiku person files. +Sorta in the spirit of mbox2mail and mail2mbox. + +It supports vcards of version 2.1, 3.0, and 4.0.— and supports all attributes +applicable to a person file (including photo [through URL or embeded image], +address, name, phone numbers, email, etc). + +My use-case is converting contacts between my Android phone and Haiku, which +uses version 2.1 vcards, so that version's been tested fairly well. +3.0 and 4.0 have only been tested on the example files in ./ex/ + + +-------------------- +USAGE +-------------------- +usage: vcard2person vcard [-h] [-d directory] [-o path] file + +People files will be created using the data from a vcard. Each vcard defined +by a given vcf/vcard file have its person file be named after the vcard's +full-name. +-o changes the output person filename +-d outputs all people files to a given directory-- useful if a file + defines multiple vcards. +-h prints this message. + + +usage: person2vcard [-h] [-v version] [-o path] person + +Creates a vcard from the given person file's attributes. By default will +output to a file of the same name with a .vcf file-extension. +-o changes output vcard filename +-v specify vcard version: 2.1, 3.0, or 4.0. Defaults to 4.0. + a random URL, and use it as the vcard's photo URL. +-h print this message. + + +-------------------- +CUSTOMIZATION +-------------------- +It should be pretty easy to work with your own custom attributes-- a couple +examples are in './patches/', for a META:birthday attribute (BDAY) and for +a META:group (X-ANDROID-CUSTOM:vnd.android.cursor.item). + + +-------------------- +BORING STUFF +-------------------- +Author: Jaidyn Ann +License: CC0 diff --git a/ex/2.1-inv.vcf b/ex/2.1-inv.vcf new file mode 100644 index 0000000..575a996 --- /dev/null +++ b/ex/2.1-inv.vcf @@ -0,0 +1,10 @@ +BEGIN:VCARD +VERSION:2.1 +N:Klemenski;Mandy;;Ms.; +FN:Mandy Klemenski +TEL;CELL:+1-404-888-9999 +EMAIL;HOME:mommy@emailingtime.co.uk +ADR;HOME:;;1999 Duck Land Road, Candy, IN 88888, USA;;;; +ADR;WORK:;;1889 Sparrow Lane, Candy, IN 88888, USA;;;; +BDAY:1990-04-05 +END:VCARD diff --git a/ex/2.1-photo.vcf b/ex/2.1-photo.vcf new file mode 100644 index 0000000..2d0504b --- /dev/null +++ b/ex/2.1-photo.vcf @@ -0,0 +1,86 @@ +BEGIN:VCARD +VERSION:2.1 +N:Klemanto;Mandy;;Ms.; +FN:Mandy Klemanto +TEL;HOME:1-404-123-4535 +ADR;HOME:;;1903 Madoka Avenue;Charleton;Utah;98989;USA +PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEU + AAQEAAAIYAAAAAAIQAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAA + ABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh + 3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMA + UgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIA + AAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPK + nAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAA + AAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMAAwICA + wICAwMDAwQDAwQFCAUFBAQFCgcHBggMCgwMCwoLCw0OEhANDhEOCwsQFhARExQVFRUMDxcYFh + QYEhQVFP/bAEMBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ + UFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAF4AYAMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAA + AAAAAAAGBwQFCAMCAP/EAD0QAAIBAgUCBAMGAwUJAAAAAAECAwQRAAUGEiExQQcTIlFhcYEIF + DKRocEVI0IWM4Kx4QkkNENSctHw8f/EABsBAAMBAQEBAQAAAAAAAAAAAAUGBwQDAgAB/8QANB + EAAQMCBAQEBAQHAAAAAAAAAQACAwQRBSExQRITUYFhcbHBBiKRoRRS0fAVJDJCouHx/9oADAM + BAAIRAxEAPwDSgaK/BF8eiUI6j88QMtkZMrWfM6Q0c5ALQxuHsfa464n0k+W1MYfbKAexIBxB + XODTZyoLmkXtnbosZaryuGt1vqnUWfbppYKllihuLKi8L2+WB7JdM0meEZ/r3MKjL8iU/wAml + gG55BzYADphs+O/hjmuW5rmed5K0mYZPWnzailjsJIWsL/9y8X+GF/4d6ro811dp9c8y8V0VE + zeXQqLh5Nvp3A9gbflhyglc+n5sTrgDO2rQBoOlzv3TnwwTwgR6WJt1Pj1tsCfa2hvCDTetK6 + gjqNB6SyLw50uQpbPtTC888QNi6oCGuBc+sgG/UYtdR+K/h74U5GU1n9pfNc+zUrNLFSaMp6V + g20X8sBIZgjNcBTJIik9wASMD/aR+0Bq7WWq8wy3NMxb7vQztFHlsUoNPSbTYKFU7d4FwT1Bu + OLWGfaiskrJTJI7OW5JYk84OUOD85okmtnta5+pv+n2UrxHES2VzW6g/vM3J+3ktyeIX+0By6 + ANT6Fz7xFnvtvVakq6Ajr6h5UcJPyPmd+gxaaX+17p7xayyfINcU0FZJKpWkrGphGZGtyHVS2 + xhf8AEODjBcERJ5F7+4GLKgZ6OrWaBJkeJtyywGxBHf8A1wYl+H6WWOzRwnqMih9PjksEge5o + cAfI/UWK2Tknhy+ZaibTVHCathUlVCyb1iiuLkkG3HTGh/G7UNH4c+GtYqbVlWBaWlReLtYLc + fIc4Q32IfHfT9NmE+mc2fZnda4eCvlBJk9JJR2JPYA36D1XPOKb7WHiV/anXE+W0sm6hywmIW + vZpP6z9On0wlVNJM+uFPNo3O/UZZ99FQDirK2NlRG3hYwf5dO3p5pH59m3noQGJP42PucE1F6 + 6KA+6Dp8sAVaxNNKe9sMHIV83KqdybkoMNTTZqwYZI+pqnl+pHun79l3TdVqfJ82zOq1FVCoj + dYV+8VTtccm9icOmHTVJG4gq9QBJCbqm8jdjO3gZntLpHT2ZRwyN5T1EagqdxHpY/tgt1DrEZ + h92mpRK00Ml726jvid18EslY+39O2XgmyGkqC1reKwsNLHbqfFOOXI8jhFqjORJ8ycZ3+1dW6 + X0ToCeoymaifNqp/KRQipI4JClhcXbbu3EDrYA+ksMGFVqsTxktBOT15TGJ/tLV9XmniG9bVL + sR4lSFOgCKSBwT8bm3cnvfGzBaF8tU0vdYDPz8ENxV82HUbpQ4knLawuDn29UsHdpnPe5v8T8 + 8e6KkkrJxHEu9j7dBjjASwXj8R2qB1Y4Yem8kTLaVGlB+8yHlUFyfYWHP055xZ4I+M+ChtVPy + gTuu2mPDsVciNVSEoObLwD74cOR6Ko6OkMccMaoVtaw5xQadcROjVFJWRLewIpzYfPuMM6jgi + ly5aiOX+Vs8wm3IUd/fthljgYxuQSLPWTPfmUhdf6ROiM6gzajY0wMqyRTRelopV9QII5BuAb + /AAx8+ZSZlGtRI5kllG5mJvcnqcHHjHV5dX6KrFp5jUGOWMBvJcAHeo4YrY8E9DhbZFC6ZRTq + /BVe/tfj9MJONMaxwIGaouAzSSRkEm3Ty/6u1Uu+FwBc7Txhh6ZYNp+kbvt/fADIvHx+GDLSU + t9O049hbC435mkKl4I4Mqjf8p9QpdBmRyvRQGWu8EJrAAYGsDZW5/XFbPrDMtpBra5to6eccW + lU9M/h8s9KVanFeFV06H0N0wIud6u17Ai17Y5tsS423WisqJYAyON/9o000Vx/a/MpEBNbWG3 + Yzk/pgD1nDLqXUFKtS8jCOIXeX1EAm3f5jDd05lWWRUCSR1UFKUi8ySpm/mSM3ZET3+JNh3tx + hC641h9/1Q1RAzeXCxVd5F7dOoA7dPa/0wQoC0z5tyGqE48yoiomiSa7naDXvf8A13UGkyhqL + UaoXEyeZtViu2wB6W7dMMGupKpsuZKKdqapI/vEHNva/b6YGNP1GX1SwJHUB5mcNsYepG2+od + Onx6frhlZMFU2kTdfpcYe6dgvbYqNVszxZxGYQrkNZmOUUs9OIa6WVvLPnySKsai93AG0k34A + 5HxB6YP8AT+qs1qqGqoYZCtXIg8skgkWYe/wvbHCrow0Mk3BWMXVb9/a2J2gsgjmzKasd55Fi + H/EKAikm1gByR369bYKlhbk05IGJ2zkve0AhBGfPqKaat+8eauTl/LZaiMox9QZCeSCRt5tYc + jr25QEPC42geojjp17YavidGiaYEKuvnVEwsCedoBN/zt+eFVQBmpo3b+pQ2ETG3gvt4qjYAC + WcVrZFcnAAv7dsE2iZL5JbsHYW9ucULQ3xcaKc/wAPnQ9Flbp88L0Qu0p+w13BVt8QVdRS0tR + 4PB6NU8gZsApQWFvLbp+uBRxaBubC2CvLmgm8GHlgA8k5yoFhYf3TYEa6ojp6OWSRgiKvJOOE + Ys54HUr3Vu4msefyBDer89koKNIIZCjup3EHovT/AMjCyqAdxYg8m18Xud5icwriexsApbgDs + P3P1xVFDIHe2+MW/EebHn9j+eGWmi4GZpJqJjI7M6L3kFU9FmsEi3Nvbvh/ZRPHWwQzRuGNhf + CGy+jaRlmT0BCG5HTngn37e3XDbyN3Slp6iE+lgNyg3+owy0JNiEn4rYlrkXagZ6PJmNNBLVV + DEeiEXIWxubdz0FvniiyTUGoKaop1pspmaFiRIz3FjcAWQL+K1x15+HTBHldbuljWc+Wb8M34 + Tg6zPUuU6N05NmuaV0flxpZEQAvK/ZEFxdj/AKmwuQckF2cd7Dslujm5b+Tyw4k65pe+JuYSV + uc0MLMFakprSR2sVd13Pc9CLLH8ucDMXpiQDgBQBx8MU2W6nm1U89ZWH/eJpWaQAkgXN7C5J2 + 9ABfoAMXO8W4OJZXPdLM4u3P22Vhw2NsMLWt2Fu+68ym4288+2LHRcm2KtTssp/wA8VErlWBG + JGQytHHmW0+otx+Qxmh3CNUkvLqWyHa/or8aiynKPAtUEgHnZuzwRRi7SBI1DWHa29epA5OFF + qDU8+dRvFTAQ0yc82LPzxfnj5c491GWSmEu+1GX8Q5Cq9+R7cH24tjzlWQVcjyEpsl8ssVkW1 + wBu5uPhh8ofh5jDd9yXElJdZjr52gNs0NAb52QuYm27im42J4PWxx0lmilb0xkAjav6DBjJkb + vDTkwxvJJKYQWutrqWubfL/wB4wO1GTJTF4qifypIZGV4URma3QMOgIv2LD5HBCbDDCfl0QmO + sbLqV7yxBJPEDtUfgZQeSO35/thraXgWkpoIB6/TYC/bt+2FZQVlGUNLNSrCrNeGU23ix5vJb + k/Ai3wGDvIWqctttnjqNt2uvRgTzY/Pg/EdxYnrDEIXB4zHp5oTiIdK0t3TIz7OMt03k8s3lC + SVV4Um4B/PGaM8rZM1rpquVi89Q+9uenYD6DB1rfUEk9G6Mltx/q56YWkkxLFr3bucC8SlD3h + rdAtmCUxhiL3alXGU5m+XwwzwKN8TukhHdTYrf9fywa5Zqb747gpuCqWsgNz8vlhf5HVmkllJ + RZI5ABIjKCCnN+3X2PYgYn6fzOKgzunmmsIwwDFluB8P2+uMEVPTSubzxkculu6OPmniDuUdM + 0xY6yCrRnhkD2APcdfgecd9OAyT5jEDzYEfliFS+WldNTxs7FowyqR6ebMCLmwNyw59xiZpiQ + HNa3aeWQWP0wOxHDf4dOAw3a4XH1zCM4PW/jXN4hmDY9xqhqpeqpqctIf5zm21DcL04N+5PXp + 3xIo80EFbE7h/KBLItO42Ne9lP1uLWFhY8hr4qxI8kYLtvd2LndyLm5/fEaKtNVTBkXyo4pNj + IvU83vfv0P6YqTSGWsVPwziBuES5jmdZS5qyPtbyZrhVI4423t2B9ses0dJJG3U8MM0rGaQub + Nawtf5k3xW5vM8VZG67SDHC3I54I6+/THeszCaPdENjmoaMXdfwi5sL47SH5iL7rMyMDhIG36 + IeqoqWsSdtsS+WLgKzbjxzYcfP6YK8jeH+HRxrsQr61EcvzBsh5Fz8bdcCrz1EE6OFiJQ/Hnn + Ftk6fcojWRu/lQgrKrEkutz8QDzz/8xgDeIutnf9hbJ2kxgHsoWrZBNHtPoIv0PBPvgFJ5sOm + DLU2Zw1lPBLGjqCxNmtz6SP3wIxxiSXb/ANRHPthMq85SjOH3EQyVnkNO89QSpIdF3pYX6H2x + YTRpR1kFQsQqKWQ3MVxtsbjaTzyCGH+EHuDiRpKBBWFXJKkOpI6j082xHjkaamdf+Wrn57gL8 + fMD/L2xrpI2yRDi3Jt5iy+meRKQPBHuSZxJSSNDJOUhWnanSRYtxmRVslybkWVwP8Ix0yLdHq + SrUgKCvRT8TiDRVn3OnyyfaGlSNo7kA+k+nn3NiOcd8qdk1Eqsdzmki3HtfYt7DsPbHvGG/wA + o2+rXW7EX9gv3BH8Fdlv7G3uv/9k= + +END:VCARD diff --git a/ex/2.1.vcf b/ex/2.1.vcf new file mode 100644 index 0000000..7258c67 --- /dev/null +++ b/ex/2.1.vcf @@ -0,0 +1,10 @@ +BEGIN:VCARD +VERSION:2.1 +N:Klemenski;Mandy;;Ms.; +FN:Mandy Klemenski +TEL;CELL:+1-404-888-9999 +EMAIL;HOME:mommy@emailingtime.co.uk +ADR;HOME:;;1999 Duck Road;Candy;IN;888888;USA +ADR;WORK:;;1889 Sparrow Lane;Candy;IN;88888;USA +BDAY:1990-04-05 +END:VCARD diff --git a/ex/4.0.vcf b/ex/4.0.vcf new file mode 100644 index 0000000..59f4dc5 --- /dev/null +++ b/ex/4.0.vcf @@ -0,0 +1,20 @@ +BEGIN:VCARD +VERSION:4.0 +NOTE:A super-valid v4.0 vcard. +N:Alderson;Darlene;;Ms.; +FN:Darlene +Alderson +ORG:Fun Society, Inc. +PHOTO;MEDIATYPE=image/jpeg:http://coinsh.red/u/darlene.jpg +TEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212 +TEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212 +ADR;TYPE=WORK;PREF=1;LABEL="3027 West 12th Street, Coney Island\nNew York City\, NY\nUnited States of America":;; +3027 West 12th Street, Coney Island; +New York City;NY; +1234; +United States of America +ADR;TYPE=HOME;LABEL="217 E. Broadway\nNew York City\, NY\nUnited States of America":;;217 E. Broadway;New York City;NY;1234;United States of America +EMAIL:d0loreshaze@protonmail.com +REV:20160428T195243Z +x-qq:21588891 +END:VCARD diff --git a/patches/android_group-person2vcard.patch b/patches/android_group-person2vcard.patch new file mode 100644 index 0000000..082fa60 --- /dev/null +++ b/patches/android_group-person2vcard.patch @@ -0,0 +1,12 @@ +69a70,71 +> ## CUSTOM +> set_vcard_group "$vc" "$(catattr -d META:group "$person" 2>/dev/null)" +98a101,108 +> } +> +> ## CUSTOM +> function set_vcard_group { +> local vcard="$1" +> local group="$2" +> simple_set_vcard "$vcard" "X-ANDROID-CUSTOM" \ +> "vnd.android.cursor.item/relation;${group};1;;;;;;;;;;;;;" diff --git a/patches/android_group-vcard2person.patch b/patches/android_group-vcard2person.patch new file mode 100644 index 0000000..bc0cba9 --- /dev/null +++ b/patches/android_group-vcard2person.patch @@ -0,0 +1,14 @@ +191a192,201 +> ## CUSTOM +> function vcard_group { +> local vcard="$1" +> grep "X-ANDROID-CUSTOM:vnd.android.cursor.item/relation;" "$vcard" \ +> | sed 's%X-ANDROID-CUSTOM:vnd.android.cursor.item/relation;%%' \ +> | sed 's%;.*%%' \ +> | clean +> } +> +> +203a214,215 +> ## CUSTOM +> addattr -t string META:group "$(vcard_group "$vcard")" "$person" diff --git a/patches/bday-person2vcard.patch b/patches/bday-person2vcard.patch new file mode 100644 index 0000000..1760e1e --- /dev/null +++ b/patches/bday-person2vcard.patch @@ -0,0 +1,11 @@ +69a70,71 +> ## CUSTOM +> set_vcard_bday "$vc" "$(catattr -d META:birthday "$person" 2>/dev/null)" +98a101,107 +> } +> +> ## CUSTOM +> function set_vcard_bday { +> local vcard="$1" +> local bday="$2" +> simple_set_vcard "$vcard" "BDAY" "$bday" diff --git a/patches/bday-vcard2person.patch b/patches/bday-vcard2person.patch new file mode 100644 index 0000000..a5653fc --- /dev/null +++ b/patches/bday-vcard2person.patch @@ -0,0 +1,6 @@ +190a191,192 +> ## CUSTOM +> function vcard_bday { local vc="$1"; get_vcard_value "$vc" "BDAY" | clean; } +203a206,207 +> ## CUSTOM +> addattr -t string META:birthday "$(vcard_bday "$vcard")" "$person" diff --git a/person2vcard b/person2vcard new file mode 100755 index 0000000..9c82ceb --- /dev/null +++ b/person2vcard @@ -0,0 +1,289 @@ +#!/boot/system/bin/bash +# -------------------------------------- +# PERSON2VCARD +# simple script for convertining a person +# into a vcard. +# CC0, jadedctrl@teknik.io +# -------------------------------------- + +# PERSON INPUT +# -------------------------------------- +# convert a given vcard file into a person file +function person_to_vcard { + local person="$1" + + if test -n "$OUTPUT_PATH"; then vcard="$OUTPUT_PATH" + else vcard="${person}.vcf" + fi + + make_vcard "$vcard" + populate_vcard_person "$vcard" "$person" + finish_vcard "$vcard" +} + +# return a properly-formatted colon-delimited address, and a pretty address for +# labels. the former on first line, latter on the second. +function person_address { + local person="$person" + + local address="$(catattr -d META:address "$person" 2>/dev/null)" + local city="$(catattr -d META:city "$person" 2>/dev/null)" + local state="$(catattr -d META:state "$person" 2>/dev/null)" + local zip="$(catattr -d META:zip "$person" 2>/dev/null)" + local country="$(catattr -d META:country "$person" 2>/dev/null)" + + if test -z "$city" -a -z "$address"; then return 2; fi + echo ";;${address};${city};${state};${zip};${country}" + echo "${address}, ${city}, ${state} ${zip}, ${country}" +} + + +# VCARD OUTPUT +# -------------------------------------- +# create a fresh, blank vcard +function make_vcard { + local vcard="$1" + printf 'BEGIN:VCARD\nVERSION:%s\n' "$VERSION" >> "$vcard" + addattr -t mime BEOS:TYPE text/x-vcard "$vcard" +} + +# wrap up the current vcard +function finish_vcard { + local vcard="$1" + date +"REV:%Y%m%d" >> "$vcard" + echo "END:VCARD" >> "$vcard" +} + +# populate a vcard with a person's attributes +function populate_vcard_person { + local vc="$1" + local person="$2" + set_vcard_name "$vc" "$(catattr -d META:name "$person" 2>/dev/null)" + set_vcard_org "$vc" "$(catattr -d META:company "$person" 2>/dev/null)" + set_vcard_url "$vc" "$(catattr -d META:url "$person" 2>/dev/null)" + set_vcard_email "$vc" "$(catattr -d META:email "$person" 2>/dev/null)" + set_vcard_phone "$vc" "$(catattr -d META:wphone "$person" 2>/dev/null)" "WORK" + set_vcard_phone "$vc" "$(catattr -d META:hphone "$person" 2>/dev/null)" "HOME" + set_vcard_nick "$vc" "$(catattr -d META:nickname "$person" 2>/dev/null)" + set_vcard_address "$vc" "$(person_address "$person")" "HOME" + set_vcard_photo "$vc" "$person" +} + +# set the name and full-name of a vcard. +function set_vcard_name { + local vcard="$1" + local name="$2" + simple_set_vcard "$vcard" "N" "$(infer_name "$name")" + simple_set_vcard "$vcard" "FN" "$name" +} + +# set the email of a vcard. +function set_vcard_email { + local vcard="$1" + local email="$2" + simple_set_vcard "$vcard" "EMAIL" "$email" +} + +# set a vcard's url attribute. +function set_vcard_url { + local vcard="$1" + local url="$2" + simple_set_vcard "$vcard" "URL" "$url" +} + +# set a vcard's organization attribute. +function set_vcard_org { + local vcard="$1" + local org="$2" + simple_set_vcard "$vcard" "ORG" "$org" +} + +# set a vcard's nick. only for 3.0 and 4.0; 2.1 doesn't support NICKNAME. +function set_vcard_nick { + local vcard="$1" + local nick="$2" + if test -z "$nick"; then return 2; fi + + if test "$VERSION" = "3.0" -o "$VERSION" = "4.0"; then + echo "NICKNAME:${nick}" >> "$vcard" + fi +} + +# set a vcard's phone-number of the given type. +function set_vcard_phone { + local vcard="$1" + local number="$2" + local type="$3" + if test -z "$number"; then return 2; fi + if test -z "$type"; then type="HOME"; fi + + local prefix="" + if test "$VERSION" = "2.1"; then prefix="TEL;${type};VOICE:" + elif test "$VERSION" = "3.0"; then prefix="TEL;TYPE=${type},VOICE:" + elif test "$VERSION" = "4.0" + then prefix="TEL;TYPE=${type},voice;VALUE=uri:tel:" + fi + + echo "${prefix}${number}" >> "$vcard" +} + +# sets a vcard's address and address label +# ($addresses is a two-lined string, with the colon-delimited address on top +# and the user-friendly comma-delimited on bottom.) +function set_vcard_address { + local vcard="$1" + local addresses="$2" + local type="$3" + + local address="$(echo "$addresses" | head -1)" + local label="$(echo "$addresses" | tail -1)" + local addrstr="" + local labelstr="" + + if test "$VERSION" = "2.1" -o "$VERSION" = "3.0"; then + labelstr="LABEL;TYPE=${type}:${label}" + fi + + if test "$VERSION" = "2.1"; then + addrstr="ADR;${type}:${address}" + elif test "$VERSION" = "3.0"; then + addrstr="ADR;TYPE=${type}:${address}" + elif test "$VERSION" = "4.0"; then + addrstr="ADR;TYPE=${type};LABEL=\"${label}\":${address}" + fi + + if test -z "$address" -o -z "$label"; then return 2; fi + + echo "$addrstr" >> "$vcard" + if test -n "$labelstr"; then + echo "$labelstr" >> "$vcard" + fi +} + +# embeds the given photo into the vcard file (base64), if possible +function set_vcard_photo { + local vcard="$1" + local photo="$2" + if is_image "$photo"; then ls>/dev/null; else return 2; fi + + local mime="$(file --mime-type "$photo")" + local type="$(file "$photo" | awk -F : '{print $1}' | awk '{print $1}')" + echo >> "$vcard" + if test "$VERSION" = "2.1"; then + echo "PHOTO;${type};ENCODING=BASE64:$(base64 -w 79 "$photo")" \ + >>"$vcard" + elif test "$VERSION" = "3.0"; then + echo "PHOTO;TYPE=${type};ENCODING=b:$(base64 -w 79 "$photo")" \ + >> "$vcard" + elif test "$VERSION" = "4.0"; then + echo "PHOTO:data:${mime};base64,$(base64 -w 79 "$photo")" \ + >> "$vcard" + fi + echo >> "$vcard" +} + +# generic function for setting simple attrs of vcards. +function simple_set_vcard { + local vcard="$1"; local key="$2"; local val="$3" + if test -z "$val"; then return 2; fi + echo "${key}:${val}" >> "$vcard" +} + + + +# MISC +# -------------------------------------- +# takes a trad. comma-delimited address and converts to vcard format +function infer_name { + local names="$1" + + local inferred_name="" + local honorific="" + local slot_count=5 + local name_count="$(echo "$names" | wc -w)" + for name in $names; do + if is_honorific "$name"; then + honorific="$name" + else + inferred_name="${name};${inferred_name}" + fi + done + + local i="" + if test "$name_count" -ne "$slot_count"; then + i="$((slot_count - $name_count - 1))" + + while test "$i" -ne 0; do + inferred_name="${inferred_name};" + i="$((i - 1))" + done + + if test -n "$honorific" -a "$name_count" -lt "$slot_count"; then + inferred_name="${inferred_name}${honorific};" + fi + fi + + echo "$inferred_name" +} + +# ok so this is super language-dependant and is omega cringe +# you'll probably have to modify this for your locale +function is_honorific { + local name="$1" + + local honorifics="ms. mrs. mr. s-ro s-ano s-ino" + if echo "$honorifics" | grep -i "$name" > /dev/null; then + return 0 + else + return 1 + fi +} + +# check whether or not a file is an image +function is_image { + local file="$1" + file "$file" | grep "image" > /dev/null +} + + +# INVOCATION +# -------------------------------------- +function help { + echo "usage: $(basename $0) [-h] [-v version] [-o path] person" + echo + echo \ +"Creates a vcard from the given person file's attributes. By default will +output to a file of the same name with a .vcf file-extension. +-o changes output vcard filename +-v specify vcard version: 2.1, 3.0, or 4.0. Defaults to 4.0. + a random URL, and use it as the vcard's photo URL. +-h print this message." + exit 1 +} + + +VERSION="4.0" +OUTPUT_PATH="" +while getopts ":hUo:v:" arg; do + case $arg in + o) OUTPUT_PATH="$OPTARG";; + v) if test "$OPTARG" = "4.0" -o "$OPTARG" = "3.0" \ + -o "$OPTARG" = "2.1"; then + VERSION="$OPTARG" + else + echo "Invalid version number." >&2 + exit 3 + fi;; + h) help;; + esac +done + +shift $(($OPTIND - 1)) +remaining_args="$@" + +if test -z "$1"; then help +else PERSON="$1" +fi + + +person_to_vcard "$PERSON" diff --git a/vcard2person b/vcard2person new file mode 100755 index 0000000..c5ba482 --- /dev/null +++ b/vcard2person @@ -0,0 +1,429 @@ +#!/boot/system/bin/bash +# -------------------------------------- +# VCARD2PERSON +# simple script for convertining a vcard +# into a haiku person file. +# CC0, jadedctrl@teknik.io +# -------------------------------------- + + +# VCARD INPUT +# -------------------------------------- +# convert a given vcard file into a person file +function vcard_to_person { + local vcard="$1" + local person="$2" + if test -n "$OUTPUT_PATH"; then person="$OUTPUT_PATH"; fi + if test -n "$OUTPUT_DIR"; then person="${OUTPUT_DIR}/${person}"; fi + + make_person "$person" + populate_person_vcard "$person" "$vcard" +} + +# sometimes vcf files contain multiple vcard definitions. +# if so, then process each vcard individually. +function vcf_to_persons { + orig_vcf="$1" + + vcf="$(mktemp -t 'v2p-XXXX')" + vcard="$(mktemp -t 'v2p-XXXX')" + cp "$orig_vcf" "$vcf" + + while grep "END:VCARD" "$vcf" > /dev/null; do + sed '/END:VCARD/q' "$vcf" \ + > "$vcard" + + vcard_to_person "$vcard" "$(vcard_name "$vcard"| remove_cr)" + + tail -n +2 "$vcf" \ + | sed '/BEGIN:VCARD/,$!d' \ + > "$vcf.tmp" + mv "$vcf.tmp" "$vcf" + + + done + + rm "$vcf" "$vcard" +} + +# get a given value from a vcard +function get_vcard_value { + local file="$1" + local key="$2" + +# local matching="$(grep "^${key}" "$file")" + local matching="$(grep -n "^${key}" "$file" | awk -F : '{print $1}')" + echo "===${matching}">>/tmp/dad + + if test "$(line_length "$matching")" -gt 1; then + matching="$(select_match "$file" "$key" "$matching")" + fi + + local end_line="$(next_match "$file" "$matching" '^.*[A-Z a-z "]:')" + + cat "$file" \ + | get_line "$matching" \ + | cut --complement -d ':' -f 1 + + cat "$file" \ + | line_range "$((matching + 1))" "$((end_line - 1))" +} + +# print how many matches a given key has +function vcard_match_count { + local file="$1" + local key="$2" + line_length "$(grep "^${key}" "$file")" +} + +# select a given match; for when there are mulitple possible slots +function select_match { + local file="$1" + local key="$2" + local matches="$3" + local match_count="$(line_length "$matches")" + + echo "There are a few matching slots for ${key}." 1>&2 + echo "Which one do you want to use?" 1>&2 + echo "(0 for skip)" 1>&2 + + local prompt="[0-${match_count}] >> " + select_line "$file" "$prompt" "$matches" +} + +# return a given field-number of vcard's adr +function vcard_adr_value { + local adr="$1" + local index="$2" + + echo "$adr" \ + | awk -v ind="$index" -F ';' '{print $ind}' +} + +# get a vcard's telephone number +function vcard_tel { + local file="$1" + get_vcard_value "$file" "TEL" \ + | sed 's%tel:%%' \ + | clean +} + +# puts a vcard's photo to a given output, if it exists +function vcard_photo { + local vcard="$1" + local output="$2" + + local photo_value="$(get_vcard_value "$vcard" "PHOTO")" + if test -z "$photo_value"; then return 2; fi + + if echo "$photo_value" | grep "http://">/dev/null; then + vcard_photo_url "$photo_value" "$output" + else + vcard_photo_base64 "$photo_value" "$output"; + fi +} + +# if vcard has a url to a photo (and it's an image), save to output file +function vcard_photo_url { + local photo="$1" + local output="$2" + + local uri="$(echo "$photo" | sed 's%.*http%http%')" + if echo "$uri" | grep http > /dev/null; then + wget --quiet -O "${output}.tmp" "$uri" + if file "${output}.tmp" | grep "image" > /dev/null; then + cat "${output}.tmp" > "$output" + fi + rm "$output.tmp" + fi +} + +# tries to decode base64 photo from a vcard, save to an output file +# if the GNU base64 decoder fails, tries the openssl decoder. +# ... if *that* fails, tries the python one. +# (can you tell I really want contact photos? lmao) +function vcard_photo_base64 { + local photo="$1" + local output="$2" + + echo "$photo" \ + | clean \ + | base64 -d \ + 2> /dev/null \ + > "${output}.tmp" + local result="$?" + + if test "$result" -ne 0; then + echo "$photo" \ + | openssl enc -base64 -d \ + 2> /dev/null \ + > "${output}.tmp" + result="$?" + fi + + if test "$result" -ne 0; then + echo "$photo" \ + | clean \ + | python -m base64 -d \ + 2> /dev/null \ + > "${output}.tmp" + result="$?" + fi + + if test "$result" -eq 0; then + cat "${output}.tmp" > "${output}" + fi + rm "${output}.tmp" +} + +# pretty self-explanatory +function vcard_version { local v="$1"; get_vcard_value "$v" "VERSION" | clean; } +function vcard_name { local vc="$1"; get_vcard_value "$vc" "FN" | clean; } +function vcard_org { local vc="$1"; get_vcard_value "$vc" "ORG" | clean; } +function vcard_url { local vc="$1"; get_vcard_value "$vc" "URL" | clean; } +function vcard_email { local vc="$1"; get_vcard_value "$vc" "EMAIL" | clean; } +function vcard_adr { local vc="$1"; get_vcard_value "$vc" "ADR" | clean; } +function adr_address { local adr="$1";vcard_adr_value "$adr" 3 | clean; } +function adr_city { local adr="$1";vcard_adr_value "$adr" 4 | clean; } +function adr_state { local adr="$1";vcard_adr_value "$adr" 5 | clean; } +function adr_zip { local adr="$1";vcard_adr_value "$adr" 6 | clean; } +function adr_country { local adr="$1";vcard_adr_value "$adr" 7 | clean; } + + + +# PERSON OUTPUT +# -------------------------------------- +# populate the given person with the data of the given vcard +function populate_person_vcard { + local person="$1" + local vcard="$2" + + addattr -t string META:name "$(vcard_name "$vcard")" "$person" + addattr -t string META:company "$(vcard_org "$vcard")" "$person" + addattr -t string META:email "$(vcard_email "$vcard")" "$person" + + populate_person_telephone "$person" "$vcard" + populate_person_address "$person" "$vcard" + vcard_photo "$vcard" "$person" +} + +# populate a person with a vcard's address data +function populate_person_address { + local person="$1" + local vcard="$2" + + local adr="$(vcard_adr "$vcard")" + + # some implementations (wrongly) put all address info in the address + # slot, delimiting with commas and leaving other slots blank. + # we can try and work around that. + if test -n "$(adr_address "$adr")" -a -z "$(adr_city "$adr")"; then + if echo "$adr" | grep "," > /dev/null; then + adr="$(infer_comma_address "$adr")" + fi + fi + + addattr -t string META:address "$(adr_address "$adr")" "$person" + addattr -t string META:city "$(adr_city "$adr")" "$person" + addattr -t string META:state "$(adr_state "$adr")" "$person" + addattr -t string META:zip "$(adr_zip "$adr")" "$person" + addattr -t string META:country "$(adr_country "$adr")" "$person" +} + +# populate a person with a vcard's phone data +function populate_person_telephone { + local person="$1" + local vcard="$2" + + if test "$(vcard_match_count "$vcard" "TEL")" -eq 1; then + addattr -t string META:hphone "$(vcard_tel "$vcard")" "$person" + elif test "$(vcard_match_count "$vcard" "TEL")" -gt 1; then + echo "Home number:" + addattr -t string META:hphone "$(vcard_tel "$vcard")" "$person" + echo "Work number:" + addattr -t string META:wphone "$(vcard_tel "$vcard")" "$person" + fi +} + +# make a new person file +function make_person { + local file="$1" + + touch "$file" + addattr -t mime BEOS:TYPE application/x-person "$file" +} + + + +# MISC +# -------------------------------------- +# takes a trad. comma-delimited address and converts to vcard format +function infer_comma_address { + local addr="$1" + + local addr_split="$(echo "$addr"| tr -d ';'| tr ',' '\n'| sed 's%^ %%')" + local city_zip_line="$(echo "$addr_split" | grep '[0-9]$')" + local zip="$(echo "$city_zip_line" | awk -F ' ' '{print $NF}')" + + echo ";;${addr_split}" \ + | sed 's% '"$zip"'%\n'"$zip"'%' \ + | tr '\n' ';' \ + | sed 's%\;$%%' +} + +# have the user select a given line (enumerates output, captive) +# also outputs everything to stderr +function select_line { + local file="$1" + local prompt="$2" + local line_nums="$3" + + local index=1 + for line in $line_nums; do + printf '%s ' "$index" >&2 + cat "$file" \ + | get_line "$line" \ + >&2 + index="$((index + 1))" + done + + index="$(select_number 0 $(line_length "$line_nums") "$prompt")" + echo "$line_nums" \ + | get_line "$index" +} + +# have the user choose a number within the given range +# ouputs everything to stderr except the number +function select_number { + local min=$1 + local max=$2 + local prompt="$3" + + printf "$prompt" >&2 + read response + + if test "$response" -le "$max" -a "$response" -ge "$min"; then + echo $response + else + select_number $min $max "$prompt" + fi +} + +# echoes everything piped into it. +# useful for taking piped info in a shell function. +function reade { + local stack="" + while read input; do + stack="$(printf '%s\n%s' "$stack" "$input")" + done + echo "$stack" +} + +# self-explanatory, no? +function escape_spaces { sed 's% %\\ %g'; } +function remove_cr { tr -d '\r'; } +function replace_nl { tr '\n' ' '; } +function trim { awk '{$1=$1};1'; } + +# does some handy sanitization-work +function clean { + remove_cr \ + | replace_nl \ + | trim +} + +# check whether or not a file is an image +function is_image { + local file="$1" + file "$file" | grep "image" > /dev/null +} + +# prints the amount of lines a string has +function line_length { + local lines="$1" + echo "$lines" \ + | wc -l +} + +# prints the line at given index +function get_line { + local line_index="$1" + awk -v ind="$line_index" 'NR==ind' +} + +# prints lines after given index (inclusive) +function after_line { + local line_index="$1" + awk -v ind="$line_index" 'NR>=ind' +} + +# prints lines before given index (inclusive) +function before_line { + local line_index="$1" + awk -v ind="$line_index" 'NR<=ind' +} + +# prints a range of lines (inclusive) +function line_range { + local min="$1" + local max="$2" + awk -v min="$min" -v max="$max" 'min <= NR && NR <= max' +} + +# prints the line index of the next match following a given line index +function next_match { + local file="$1" + local after_index="$2" + local regex="$3" + + cat "$file" \ + | after_line "$((after_index+1))" \ + | grep -n "${regex}" \ + | head -1 \ + | awk -v last="$after_index" -F : '{print last + $1}' \ + | bc +} + + + +# INVOCATION +# -------------------------------------- +function help { + echo "usage: $(basename $0) vcard [-h] [-d directory] [-o path] file" + echo + echo \ +"People files will be created using the data from a vcard. Each vcard defined +by a given vcf/vcard file have its person file be named after the vcard's +full-name. +-o changes the output person filename +-d outputs all people files to a given directory-- useful if a file + defines multiple vcards. +-h prints this message." + exit 1 +} + + +OUTPUT_DIR="" +OUTPUT_PATH="" +while getopts ":hd:o:" arg; do + case $arg in + o) OUTPUT_PATH="$OPTARG";; + d) if test -d "$OPTARG"; then + OUTPUT_DIR="$OPTARG" + else + echo "Directory does not exist." >&2 + exit 3 + fi;; + h) help;; + esac +done + +shift $(($OPTIND - 1)) +remaining_args="$@" + +if test -z "$1"; then help +else VCARD="$1" +fi + + +vcf_to_persons "$VCARD"