#!/bin/bash set -euo pipefail database="$1" history_db="$HOME/.purple/chatty/db/chatty-history.db" CHATTY_ID_PHONE_VALUE=1 MMS_SMS_USERID=1 PROTOCOL_MMS_SMS=1 THREAD_DIRECT_CHAT=0 THREAD_GROUP_CHAT=1 THREAD_VISIBILITY_VISIBLE=0 CHATTY_DIRECTION_IN=1 CHATTY_DIRECTION_OUT=2 MESSAGE_TYPE_IMAGE=9 MESSAGE_TYPE_TEXT=1 MESSAGE_TYPE_MMS=12 MESSAGE_STATUS_DELIVERED=4 MESSAGE_STATUS_DELIVERY_FAILED=7 MESSAGE_STATUS_READ=5 CHATTY_FILE_DOWNLOADED=1 function join_by { local IFS="$1"; shift; echo "$@"; } # This must be adapted after your country code country_code=+33 country_regex='^0([0-9]{9})$' function normalize_address () { address="$1" if [[ "$address" =~ $country_regex ]]; then address="$country_code${BASH_REMATCH[1]}" fi echo "$address" } function add_user () { address="$1" alias="$2" value_address="'$address'" value_alias="$([ -z "$alias" ] && echo null || echo "'$alias'")" userid="$(sqlite3 "$history_db" "insert or ignore into users(username, type, alias) VALUES ( $value_address, $CHATTY_ID_PHONE_VALUE, $value_alias) ON CONFLICT(username,type) DO UPDATE SET alias=coalesce($value_alias,alias); select id from users where username=$value_address and type=$CHATTY_ID_PHONE_VALUE;")" echo "$userid" } declare -A aliases # Import SMS while read msgdata; do echo "$msgdata" IFS='|' read -a msgdata_array <<<"$msgdata" msgid="${msgdata_array[0]}" address="$(normalize_address "${msgdata_array[1]}")" display_name="${msgdata_array[2]}" date="${msgdata_array[3]}" direction="${msgdata_array[4]}" msg_status="${msgdata_array[5]}" aliases["${address}"]="$display_name" if [ "$direction" = "$$CHATTY_DIRECTION_OUT" ]; then if [ "$msg_status" = 0 ]; then msg_status="$MESSAGE_STATUS_DELIVERED" else msg_status="$MESSAGE_STATUS_DELIVERY_FAILED" fi else msg_status="$MESSAGE_STATUS_READ" fi sender_id="$(add_user "$address" "${aliases["$address"]}")" thread_name="'$address'" thread_alias="'${aliases["$address"]}'" if [ "$thread_alias" = "''" ]; then thread_alias=null fi local_user_accountid="$(sqlite3 "$history_db" "SELECT id from accounts where user_id=$MMS_SMS_USERID and protocol=$PROTOCOL_MMS_SMS;")" threadid="$(sqlite3 "$history_db" "INSERT INTO threads(name,alias,account_id,type,visibility) VALUES ( $thread_name, $thread_alias, $local_user_accountid, $THREAD_DIRECT_CHAT, $THREAD_VISIBILITY_VISIBLE) ON CONFLICT(name,account_id,type) DO UPDATE SET alias=$thread_alias, visibility=$THREAD_VISIBILITY_VISIBLE; select id from threads where name=$thread_name and account_id=$local_user_accountid and type=$THREAD_DIRECT_CHAT;")" sqlite3 "$history_db" "INSERT OR IGNORE INTO thread_members(thread_id, user_id) VALUES ( $threadid, $sender_id);" # Retreive text message body="$(sqlite3 "$database" "SELECT body FROM sms where _id=$msgid;" | sed "s/'/''/g")" body="'${body:-}'" body_type="$MESSAGE_TYPE_TEXT" # Create message record uid="'$(sqlite3 "$history_db" "select substr(u,1,8)||'-'||substr(u,9,4)||'-4'||substr(u,13,3)||'-'||v||substr(u,17,3)||'-'||substr(u,21,12) from (select lower(hex(randomblob(16))) as u, substr('89ab',abs(random()) % 4 + 1, 1) as v)")'" messageid="$(sqlite3 "$history_db" "INSERT INTO messages(uid,thread_id,sender_id,body,body_type,direction,time,preview_id,status) VALUES( $uid, $threadid, $sender_id, $body, $body_type, $(( -(direction*2-3) )), $date, null, $msg_status) ON CONFLICT (uid,thread_id,body,time) DO UPDATE SET status=$msg_status; select id from messages where uid=$uid;")" done <<<"$(sqlite3 "$database" "select _id, address, display_name, date/1000, type, status from sms order by cast(_id as integer);")" # import MMS while read msgdata; do echo "$msgdata" IFS='|' read -a msgdata_array <<<"$msgdata" msgid="${msgdata_array[0]}" uid="'${msgdata_array[1]}'" date="${msgdata_array[2]}" subject="${msgdata_array[3]:-}" if [ -n "$subject" ]; then subject="'$subject'" else subject=null fi IFS='|' read -a sender <<<$(sqlite3 "$database" "select address, display_name from sender where msg_id='$msgid';") address="$(normalize_address "${sender[0]}")" display_name="${sender[1]:-}" users=() if [ -z "$address" ]; then direction=$CHATTY_DIRECTION_OUT sender_id=null msg_status="$MESSAGE_STATUS_DELIVERED" while read line; do IFS='|' read -a recipient <<<"$line" users+=("$(normalize_address "${recipient[0]}")") aliases["${recipient[0]}"]="${recipient[1]:-}" done <<<$(sqlite3 "$database" "select address, display_name from recipient where msg_id='$msgid';") else # We don't collect recipient addresses for inbound messages # They have little value, especially when you receive MMS sent to a whole address book direction=$CHATTY_DIRECTION_IN msg_status="$MESSAGE_STATUS_READ" users=("$address") aliases["$address"]="$display_name" fi thread_aliases=() thread_members=() for user in "${users[@]}"; do thread_members+=("$(add_user "$user" "${aliases["$user"]:-}")") thread_aliases+=("${aliases["$user"]:-$user}") done sender_id="${sender_id:-${thread_members[0]}}" thread_name="'$(join_by "," "${users[@]}")'" thread_alias="'$(join_by "," "${thread_aliases[@]}")'" if [ "$thread_alias" = "$thread_name" ]; then thread_alias=null fi local_user_accountid="$(sqlite3 "$history_db" "SELECT id from accounts where user_id=$MMS_SMS_USERID and protocol=$PROTOCOL_MMS_SMS;")" if [ "${#users[@]}" -gt 1 ]; then thread_type="$THREAD_GROUP_CHAT" else thread_type="$THREAD_DIRECT_CHAT" fi threadid="$(sqlite3 "$history_db" "INSERT INTO threads(name,alias,account_id,type,visibility) VALUES ( $thread_name, $thread_alias, $local_user_accountid, $thread_type, $THREAD_VISIBILITY_VISIBLE) ON CONFLICT(name,account_id,type) DO UPDATE SET alias=$thread_alias, visibility=$THREAD_VISIBILITY_VISIBLE; select id from threads where name=$thread_name and account_id=$local_user_accountid and type=$thread_type;")" for member in "${thread_members[@]}"; do sqlite3 "$history_db" "INSERT OR IGNORE INTO thread_members(thread_id, user_id) VALUES ( $threadid, $member);" done # Concat plain text parts as the body body="$(sqlite3 "$database" "SELECT text FROM part where mid=$msgid and ct='text/plain' order by cast(_id as integer);" | sed "s/'/''/g")" body="'${body:-}'" body_type="$MESSAGE_TYPE_MMS" # Create message record messageid="$(sqlite3 "$history_db" "INSERT INTO messages(uid,thread_id,sender_id,body,body_type,direction,time,preview_id,status,subject) VALUES( $uid, $threadid, $sender_id, $body, $body_type, $(( -(direction*2-3) )), $date, null, $msg_status, $subject) ON CONFLICT (uid,thread_id,body,time) DO UPDATE SET status=$msg_status; select id from messages where uid=$uid;")" # Process binary attachements parent="${XDG_DATA_HOME:-$HOME/.local/share}/chatty" attachments_info="$(sqlite3 "$database" "SELECT ct, savepath FROM part where mid=$msgid and lower(ct) not in ('application/smil', 'text/plain') order by cast(_id as integer);")" [ -z "$attachments_info" ] || while read line; do IFS='|' read -a attachment <<<"$line" mime_type="${attachment[0]}" savepath="${attachment[1]}" name="'$(basename "$savepath")'" url="'file://$(urlencode -m "$savepath")'" path="'${savepath#$parent/}'" size="$(stat --printf="%s" "$savepath")" mime_type_id="$(sqlite3 "$history_db" "INSERT OR IGNORE INTO mime_type(name) VALUES ('$mime_type'); select id from mime_type where name='$mime_type';")" # File record fileid="$(sqlite3 "$history_db" "INSERT INTO files(name,url,path,mime_type_id,size,status) VALUES ( $name, $url, $path, $mime_type_id, $size, $CHATTY_FILE_DOWNLOADED) ON CONFLICT(url) DO UPDATE SET path=$path, size=$size, status=$CHATTY_FILE_DOWNLOADED; select id from files where url=$url;")" # File_metadata if [[ "$mime_type" =~ ^(image|video|audio)/ ]]; then raw_metadata="$( (ffmpeg -i "$savepath" 2<&1 || true) | grep -E '(Duration|Stream.* Video):')" if [[ "$mime_type" =~ ^(image|video)/ ]]; then if [[ "$raw_metadata" =~ \ ([0-9]+)x([0-9]+)\ ]]; then width="${BASH_REMATCH[1]}" height="${BASH_REMATCH[2]}" fi fi if [[ "$mime_type" =~ ^(video|audio)/ ]]; then if [[ "$raw_metadata" =~ Duration:\ ([0-9]+):([0-9]+):([0-9]+)(\.([0-9]+))?, ]]; then duration=$((${BASH_REMATCH[1]}*3600+${BASH_REMATCH[2]}*60+${BASH_REMATCH[2]})) fi fi if [ -n "${width:-}${height:-}${duration:-}" ]; then width="${width:-null}" height="${height:-null}" duration="${duration:-null}" sqlite3 "$history_db" "INSERT OR IGNORE INTO file_metadata(file_id,width,height,duration) VALUES ( $fileid, $width, $height, $duration);" fi fi sqlite3 "$history_db" "INSERT OR IGNORE INTO message_files(message_id,file_id) VALUES ( $messageid, $fileid);" done <<<"$attachments_info" done <<<"$(sqlite3 "$database" "select _id, m_id, date, sub from mms order by cast(mms._id as integer);")"