How to convert iChat message backups (.ichat, Apple Binary Property List) using Python
First, download this tool:
wget https://raw.githubusercontent.com/cclgroupltd/ccl-bplist/master/ccl_bplist.py
Now create bplist2json.py
which is based on this old gist by Benno Kruit
#!/usr/bin/env python3
"""
Convert an Apple Binary Property List (bplist) to json
"""
import ccl_bplist # https://github.com/cclgroupltd/ccl-bplist
from datetime import datetime
def clean_archive(d):
if type(d) in [dict, ccl_bplist.NsKeyedArchiverDictionary]:
return {k:clean_archive(v) for k,v in d.items() if not k.startswith('$')}
elif type(d) == ccl_bplist.NsKeyedArchiverList:
return [clean_archive(i) for i in d]
else:
return d
def bplist_dict(fobj):
"""Convert a bplist file object to python dict"""
plist = ccl_bplist.load(fobj)
ccl_bplist.set_object_converter(ccl_bplist.NSKeyedArchiver_common_objects_convertor)
archive = ccl_bplist.deserialise_NsKeyedArchiver(plist)
return clean_archive(archive)
if __name__ == '__main__':
import argparse, sys, json
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--input', '-i', nargs='?', type=argparse.FileType('rb'),
default=sys.stdin, help='default: stdin')
args = parser.parse_args()
class ExportEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, bytes):
return o.decode('iso-8859-1')
return json.JSONEncoder.default(self, o)
print(json.dumps(bplist_dict(args.input), cls=ExportEncoder, indent=4))
Now create my script extract-chatlog.py
which converts the JSON files to human readable chat logs
#!/usr/bin/env python3
import json
import sys
from datetime import datetime
# Function to format datetime string
def format_datetime(dt_str):
dt = datetime.fromisoformat(dt_str)
return dt.strftime("%Y-%m-%d %H:%M:%S")
def main(input_file):
# Read JSON data from input file
with open(input_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# Extract the messages list
messages = data[2]
# Extract chat log in a human-readable format
chat_log = []
for message in messages:
sender = message['Sender']['ID']
time = format_datetime(message['Time'])
text = message['MessageText']['NSString']
chat_log.append(f"{time} - {sender}: {text}")
# Print chat log to stdout
for log in chat_log:
print(log)
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py <input_file>")
sys.exit(1)
input_file = sys.argv[1]
main(input_file)
Now it’s time to run it. Generally, run bplist2json.py
on the .ichat
to obtain a JSON file and then run extract-chatlog.py
to obtain a text conversation log with timestamps etc.
Here’s a bash oneliner to run it on all *.ichat
files in the current directory
for i in *.ichat ; do ./bplist2json.py -i ${i} > "${i}.json" ; ./extract-chatlog.py "${i}.json" > ${i}.txt ; done