explore iMessage chat database sqlite3 and python

Today I got my first GitHub star!!!

The repo is iExport which is an innocent piece of Python to extract the contents of a OS X user's chat.db, conveniently contained out-of-sight in ~/Library/Messages/chat.db and accessible via sqlite3 chat.db.

I've annotated the script below to show you how it's done.

#!/usr/bin/env python3import base64import mimetypesimport cgiimport sqlite3from os import path # this is the path where iMessage app stores the contents locallyCHAT_DB = path.expanduser("~/Library/Messages/chat.db") # unix epoch used in queryEPOCH = 978307200# this is just some basic formatted html that colorizes the chats# the messages and images are interpolated into divs belowprint("""<!doctype html><html><head><meta charse=\"utf-8\"><style>  body { margin: 0; padding: 0; }  .message {      white-space: pre-wrap;      max-width: 800px;      padding: 10px;      margin: 10px;  }  .me { background-color: #A6DBFF; }  .buddy { background-color: #EEE; }  .message img { max-width: 800px; }</style></head><body>""")# to see the chats you have, use this util# def list_chats():#     db = sqlite3.connect(CHAT_DB)#     cursor = db.cursor()#     rows = cursor.execute("""#         SELECT chat_identifier#           FROM chat;#     """)#     for row in rows:#         export(row[0])def export_all():    db = sqlite3.connect(CHAT_DB)    cursor = db.cursor()    rows = cursor.execute("""        SELECT chat_identifier          FROM chat;    """)    for row in rows:        export(row[0])# main query—get text, timestamp, and images from messages# iterate over these rows, and base64 encode attachments# in order to display images then just interpolate them like so:# line = "<div clas=\"message from-%s\" titl=\"%s\">%s</div>" % (who, date, text)# note: remove LIMIT 1 to get all messagesdef export(chat_id):    db = sqlite3.connect(CHAT_DB)    cursor = db.cursor()    rows = cursor.execute("""          SELECT datetime(m.date + ?, 'unixepoch', 'localtime') as fmtdate,                 m.is_from_me,                 m.text,                 a.filename            FROM chat as c      INNER JOIN chat_message_join AS cm              ON cm.chat_id = c.ROWID      INNER JOIN message AS m                 ON m.ROWID = cm.message_id       LEFT JOIN message_attachment_join AS ma              ON ma.message_id = m.ROWID       LEFT JOIN attachment as a              ON a.ROWID = ma.attachment_id           WHERE c.chat_identifier = ?        ORDER BY m.date           LIMIT 1;    """, (EPOCH, chat_id))    for row in rows:        date = row[0]        who = "me" if row[1] is 1 else "contact"        if row[3]:            attachment = path.expanduser(row[3])            media_type = mimetypes.guess_type(attachment)[0]            try:                with open(attachment, "rb") as image:                    encoded_data = base64.b64encode(image.read())            except:                encoded_data = ""            text = "<img sr=\"data:%s;base64,%s\">" % (                media_type, encoded_data)            text = "<img sr=\"%s\">" % (attachment)        else:            text = cgi.escape(row[2] or '')        line = "<div clas=\"message from-%s\" titl=\"%s\">%s</div>" % (who, date, text)        print(line.encode("utf8"))# crude way to construct the document, I know :~)# you'll want to pipe the output# python ixport.py > imessages.html# and then pray that your browser is prepared# I would suggest no scrolling until all has loaded# or opening a PR to paginate the data from chunksprint("""</body></html>""")export_all()