petits-scripts-persos/android_contacts2vcf.sh

434 lines
14 KiB
Bash
Raw Permalink Normal View History

2024-01-11 15:27:23 +00:00
#!/bin/bash
# Copyright (C) 2012-2013, Stachre
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>,
# or write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301, USA.
#
#
# dump-contacts2db.sh
# Version 0.4, 2013-02-11
# Dumps contacts from an Android contacts2.db to stdout in vCard format
# Usage: dump-contacts2db.sh path/to/contacts2.db > path/to/output-file.vcf
# Dependencies: perl; base64; sqlite3 / libsqlite3-dev
function rstr {
local LENGTH=${1:-8}
LC_ALL=C tr -dc a-z0-9 </dev/urandom | head -c $LENGTH
}
function uid {
rstr 8
echo -n -
rstr 4
echo -n -
rstr 4
echo -n -
rstr 4
echo -n -
rstr 12
}
# From https://gist.github.com/fabacab/531019dd8a1816fc5968ebb4db3b298b
function contact_split {
# Bash script to separate a single vCard containing multiple contacts
# into multiple vCard files each with its own contact.
dest="."
if [ "$#" -ge 1 ] ; then
dest="$1"
fi
mkdir -p "$dest"
local card_lines
local card_guid
declare -a card_lines
while read line; do
# Append each line to the end of this array.
card_lines=("${card_lines[@]}" "$line")
# Get the contact's GUID, this becomes the file name.
if [[ "$line" =~ ^N: ]]; then
card_guid=$(echo -n "$line" | cut -d ':' -f 2)
fi
# This is the end of the card, so output it.
if [[ "$line" =~ ^END:VCARD ]]; then
printf "%s\n" "${card_lines[@]}" > "$dest/$(echo -n "$card_guid" | sed 's/;/_/g' | tr 'A-Z' 'a-z' | tr -d '\r').vcf"
unset -v card_lines
fi
done
}
# From https://github.com/stachre/dump-contacts2db
function dump-contacts2db {
# expects single argument, path to contacts2.db
if [ "$#" -ne 1 ]
then echo -e "Dumps contacts from an Android contacts2.db to stdout in vCard format\n"
echo -e "Usage: dump-contacts2db.sh path/to/contacts2.db > path/to/output-file.vcf\n"
echo -e "Dependencies: perl; base64; sqlite3 / libsqlite3-dev"
exit 1
fi
# TODO: verify specified contacts2.db file exists
# inits
declare -i cur_contact_id=0
declare -i prev_contact_id=0
NEWLINE_QUOTED=`echo -e "'\n'"`
MS_NEWLINE_QUOTED=`echo -e "'\r\n'"`
CONTACTS2_PATH=$1
# store Internal Field Separator
ORIG_IFS=$IFS
# fetch contact data
# TODO: order by account, with delimiters if possible
record_set=`sqlite3 $CONTACTS2_PATH "SELECT raw_contacts._id, raw_contacts.display_name, raw_contacts.display_name_alt, mimetypes.mimetype, REPLACE(REPLACE(data.data1, $MS_NEWLINE_QUOTED, '\n'), $NEWLINE_QUOTED, '\n'), data.data2, REPLACE(REPLACE(data.data4, $MS_NEWLINE_QUOTED, '\n'), $NEWLINE_QUOTED, '\n'), data.data5, data.data6, data.data7, data.data8, data.data9, data.data10, quote(data.data15) FROM raw_contacts, data, mimetypes WHERE raw_contacts.deleted = 0 AND raw_contacts._id = data.raw_contact_id AND data.mimetype_id = mimetypes._id ORDER BY raw_contacts._id, mimetypes._id, data.data2"`
# modify Internal Field Separator for parsing rows from recordset
IFS=`echo -e "\n\r"`
# iterate through contacts data rows
# use "for" instead of piped "while" to preserve var values post-loop
for row in $record_set
do
# modify Internal Field Separator for parsing cols from row
IFS="|"
i=0
for col in $row
do
i=$[i+1]
# contact data fields stored in generic value columns
# schema determined by "mimetype", which varies by row
case $i in
1) # raw_contacts._id
cur_contact_id="$col"
;;
2) # raw_contacts.display_name
cur_display_name=$col
;;
3) # raw_contacts.display_name_alt
# replace comma-space with semicolon
cur_display_name_alt=${col/, /\;}
;;
4) # mimetypes.mimetype
cur_mimetype=$col
;;
5) # data.data1
cur_data1=$col
;;
6) # data.data2
cur_data2=$col
;;
7) # data.data4
cur_data4=$col
;;
8) # data.data5
cur_data5=$col
;;
9) # data.data6
cur_data6=$col
;;
10) # data.data7
cur_data7=$col
;;
11) # data.data8
cur_data8=$col
;;
12) # data.data9
cur_data9=$col
;;
13) # data.data10
cur_data10=$col
;;
14) # data.data15
cur_data15=$col
;;
esac
done
# new contact
if [ $prev_contact_id -ne $cur_contact_id ]; then
if [ $prev_contact_id -ne 0 ]; then
# echo current vcard prior to reinitializing variables
# some contacts apps don't have IM fields; add to top of NOTE: field
if [ ${#cur_vcard_im_note} -ne 0 ]
then cur_vcard_note=$cur_vcard_im_note"\n"$cur_vcard_note
fi
# generate and echo vcard
if [ ${#cur_vcard_note} -ne 0 ]
then cur_vcard_note="NOTE:"$cur_vcard_note$'\n'
fi
cur_vcard=$cur_vcard$cur_vcard_nick$cur_vcard_org$cur_vcard_title$cur_vcard_tel$cur_vcard_adr$cur_vcard_email$cur_vcard_url$cur_vcard_note$cur_vcard_photo$cur_vcard_im
cur_vcard=$cur_vcard"END:VCARD"
echo $cur_vcard
fi
# init new vcard
cur_vcard="BEGIN:VCARD"$'\n'"VERSION:3.0"$'\n'
cur_vcard=$cur_vcard"N:"$cur_display_name_alt$'\n'"FN:"$cur_display_name$'\n'
cur_vcard=$cur_vcard"UID:$(uid)"$'\n'
cur_vcard_nick=""
cur_vcard_org=""
cur_vcard_title=""
cur_vcard_tel=""
cur_vcard_adr=""
cur_vcard_email=""
cur_vcard_url=""
cur_vcard_im=""
cur_vcard_im_note=""
cur_vcard_note=""
cur_vcard_photo=""
fi
# add current row to current vcard
# again, "mimetype" determines schema on a row-by-row basis
# TODO: handle following types
# * (6) vnd.android.cursor.item/sip_address
# * (7) vnd.android.cursor.item/identity (not exported by Android 4.1 Jelly Bean)
# * (13) vnd.android.cursor.item/group_membership (not exported by Android 4.1 Jelly Bean)
# * (14) vnd.com.google.cursor.item/contact_misc (not exported by Android 4.1 Jelly Bean)
case $cur_mimetype in
vnd.android.cursor.item/nickname)
if [ ${#cur_data1} -ne 0 ]
then cur_vcard_nick="NICKNAME:"$cur_data1$'\n'
fi
;;
vnd.android.cursor.item/organization)
if [ ${#cur_data1} -ne 0 ]
then cur_vcard_org=$cur_vcard_org"ORG:"$cur_data1$'\n'
fi
if [ ${#cur_data4} -ne 0 ]
then cur_vcard_title="TITLE:"$cur_data4$'\n'
fi
;;
vnd.android.cursor.item/phone_v2)
case $cur_data2 in
1)
cur_vcard_tel_type="HOME,VOICE"
;;
2)
cur_vcard_tel_type="CELL,VOICE,PREF"
;;
3)
cur_vcard_tel_type="WORK,VOICE"
;;
4)
cur_vcard_tel_type="WORK,FAX"
;;
5)
cur_vcard_tel_type="HOME,FAX"
;;
6)
cur_vcard_tel_type="PAGER"
;;
7)
cur_vcard_tel_type="OTHER"
;;
8)
cur_vcard_tel_type="CUSTOM"
;;
9)
cur_vcard_tel_type="CAR,VOICE"
;;
esac
cur_vcard_tel=$cur_vcard_tel"TEL;TYPE="$cur_vcard_tel_type":"$cur_data1$'\n'
;;
vnd.android.cursor.item/postal-address_v2)
case $cur_data2 in
1)
cur_vcard_adr_type="HOME"
;;
2)
cur_vcard_adr_type="WORK"
;;
esac
# ignore addresses that contain only USA (MS Exchange)
# TODO: validate general address pattern instead
if [ $cur_data1 != "United States of America" ]
then cur_vcard_adr=$cur_vcard_adr"ADR;TYPE="$cur_vcard_adr_type":;;"$cur_data4";"$cur_data7";"$cur_data8";"$cur_data9";"$cur_data10$'\n'
cur_vcard_adr=$cur_vcard_adr"LABEL;TYPE="$cur_vcard_adr_type":"$cur_data1$'\n'
fi
;;
vnd.android.cursor.item/email_v2)
cur_vcard_email=$cur_vcard_email"EMAIL:"$cur_data1$'\n'
;;
vnd.android.cursor.item/website)
cur_vcard_url=$cur_vcard_url"URL:"$cur_data1$'\n'
;;
vnd.android.cursor.item/im)
# handle entire string within each case to avoid unhandled cases
case $cur_data5 in
-1)
cur_vcard_im_note=$cur_vcard_im_note"IM-Custom-"$cur_data6": "$cur_data1"\n"
;;
0)
cur_vcard_im=$cur_vcard_im"X-AIM:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-AIM: "$cur_data1"\n"
;;
1)
cur_vcard_im=$cur_vcard_im"X-MSN:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-MSN: "$cur_data1"\n"
;;
2)
cur_vcard_im=$cur_vcard_im"X-YAHOO:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-Yahoo: "$cur_data1"\n"
;;
3)
cur_vcard_im=$cur_vcard_im"X-SKYPE-USERNAME:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-Skype: "$cur_data1"\n"
;;
4)
cur_vcard_im=$cur_vcard_im"X-QQ:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-QQ: "$cur_data1"\n"
;;
5)
cur_vcard_im=$cur_vcard_im"X-GOOGLE-TALK:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-Google-Talk: "$cur_data1"\n"
;;
6)
cur_vcard_im=$cur_vcard_im"X-ICQ:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-ICQ: "$cur_data1"\n"
;;
7)
cur_vcard_im=$cur_vcard_im"X-JABBER:"$cur_data1$'\n'
cur_vcard_im_note=$cur_vcard_im_note"IM-Jabber: "$cur_data1"\n"
;;
*)
# Android 2.3 Gingerbread doesn't identify service; data5==""
cur_vcard_im_note=$cur_vcard_im_note"IM: "$cur_data1"\n"
;;
esac
;;
vnd.android.cursor.item/photo)
if [ $cur_data15 != "NULL" ]; then
# Remove the prefix "X'" and suffix "'" from the sqlite3 quote(BLOB) hex output
photo=`echo $cur_data15 | sed -e "s/^X'//" -e "s/'$//"`
# Convert the hex to base64
# TODO: optimize
photo=`echo $photo | perl -ne 's/([0-9a-f]{2})/print chr hex $1/gie' | base64 --wrap=0`
cur_vcard_photo=$cur_vcard_photo"PHOTO;ENCODING=BASE64;JPEG:"$photo$'\n'
# TODO: line wrapping; Android import doesn't like base64's wrapping
# For testing
#echo $cur_data15 > "images/$cur_display_name.txt"
#echo $cur_data15 | perl -ne 's/([0-9a-f]{2})/print chr hex $1/gie' > "images/$cur_display_name.jpg"
fi
;;
vnd.android.cursor.item/note)
# "NOTE:" and trailing \n appended when vCard is finished and echoed
if [ ${#cur_vcard_note} -ne 0 ]
then cur_vcard_note=$cur_vcard_note"\n\n"$cur_data1
else cur_vcard_note=$cur_data1
fi
;;
esac
prev_contact_id=$cur_contact_id
# reset Internal Field Separator for parent loop
IFS=`echo -e "\n\r"`
done
# set Internal Field Separator to other-than-newline prior to echoing final vcard
IFS="|"
# some contacts apps don't have IM fields; add to top of NOTE: field
if [ ${#cur_vcard_im_note} -ne 0 ]
then cur_vcard_note=$cur_vcard_im_note"\n"$cur_vcard_note
fi
# generate and echo vcard
if [ ${#cur_vcard_note} -ne 0 ]
then cur_vcard_note="NOTE:"$cur_vcard_note$'\n'
fi
cur_vcard=$cur_vcard$cur_vcard_nick$cur_vcard_org$cur_vcard_title$cur_vcard_tel$cur_vcard_adr$cur_vcard_email$cur_vcard_url$cur_vcard_note$cur_vcard_photo$cur_vcard_im
cur_vcard=$cur_vcard"END:VCARD"
echo $cur_vcard
# restore original Internal Field Separator
IFS=$ORIG_IFS
}
##### Main #####
tmp="$(mktemp -d)"
echo "Get db from phone"
adb pull /data/data/com.android.providers.contacts/databases/contacts2.db "$tmp"
echo "Convert it to multiple vcard files"
dump-contacts2db "$tmp/contacts2.db" | contact_split "${1:-$HOME/.contacts/vcards}"
echo "Clean"
rm -r "$tmp"