Instantly share code, notes, and snippets.

What would you like to do?
Export chat logs from
#!/usr/bin/env python
import sys
import urllib
import urlparse
import base64
import mimetypes
import cgi
from os import path
import sqlite3
# More info (typical that I find this stuff after starting...):
# - Itterate over available chats
# - Split into days
# - Handle multiple runs (don't trash existing data)
# - Add auto-linking
# - Export media (or use base64 encoding)
# - Allow selective export
# - Match chat IDs up to names using SQLite db
CHAT_DB = path.expanduser("~/Library/Messages/chat.db")
# Apple's epoch starts on January 1st, 2001 for some reason...
# cf.
def list_chats():
db = sqlite3.connect(CHAT_DB)
cursor = db.cursor()
rows = cursor.execute("""
SELECT chat_identifier
FROM chat;
for row in rows:
def export(chat_id, date):
db = sqlite3.connect(CHAT_DB)
cursor = db.cursor()
# @@ pull date calculations out of SQLite
rows = cursor.execute("""
SELECT datetime( + ?, 'unixepoch', 'localtime') as fmtdate,
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 = ?
AND fmtdate LIKE ?
""", (EPOCH, chat_id, date))
<meta charset=\"utf-8\">
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; }
for row in rows:
date = row[0]
who = "me" if row[1] is 1 else "buddy"
if row[3]:
attachment = path.expanduser(row[3])
media_type = mimetypes.guess_type(attachment)[0]
with open(attachment, "rb") as image:
encoded_data = base64.b64encode(
text = "<img src=\"data:%s;base64,%s\">" % (
media_type, encoded_data)
text = row[2]
line = "<div class=\"message %s\" title=\"%s\">%s</div> " % (
who, date, cgi.escape(text))
def main():
# @@ Output some sort of help
if len(sys.argv) == 1:
chat_id = None
if len(sys.argv) > 1:
chat_id = sys.argv[1]
date = None
if len(sys.argv) > 2:
date = sys.argv[2]
if len(sys.argv) > 3:
# @@ Handle errors gracefully
export(chat_id, date)
if __name__ == "__main__":

This comment has been minimized.

huydbui commented May 20, 2015

could you show me how to use this script please? I ran "python" and it shows a list of phone numbers only


This comment has been minimized.

adiaznar commented Jul 27, 2015

I also have this problem, I can see a list of phone numbers only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment