└── imsgextract /imsgextract: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- mode: python; -*- 3 | """Simple script to extract Messages database from SQLite db to HTML 4 | 5 | This simple script reads a Messages database, either from OS X or from 6 | iOS (using a suitable file transfer utility, or iTunes backup), 7 | executes a predefined SQL query to obtain interesting content, and 8 | writes its contents in an HTML file that can be inspected using a 9 | browser. 10 | 11 | """ 12 | 13 | import sqlite3 14 | import itertools 15 | import sys 16 | import jinja2 17 | 18 | 19 | def render_imsg_db(db_filename): 20 | """Render an html file from an iMessage database `db_filename`.""" 21 | template = ''' 22 | 23 | 24 | 25 | QZY’s iMessage Dump 26 | 27 | 156 | 157 | 158 |
159 | 160 |

iMessage Dump

161 |
162 |

{{ pagename }}

163 | 178 | 179 | 180 | ''' 181 | 182 | conn = sqlite3.connect(db_filename) 183 | sql_results = conn.execute(''' 184 | WITH groupchat_membership AS ( 185 | SELECT cache_roomnames, 186 | group_concat(id, ', ') AS members 187 | FROM ( 188 | SELECT DISTINCT message.cache_roomnames, handle.id 189 | FROM message 190 | JOIN handle ON message.handle_id=handle.ROWID 191 | WHERE message.cache_roomnames IS NOT NULL AND message.is_from_me=0 192 | ) 193 | GROUP BY cache_roomnames 194 | ), 195 | unsorted_messages AS ( 196 | SELECT message.ROWID AS rowid, 197 | CASE WHEN message.cache_roomnames IS NOT NULL THEN groupchat_membership.members ELSE handle.id END AS recipient, 198 | CASE WHEN message.is_from_me THEN 'Me' ELSE handle.id END AS sender, 199 | message.date, 200 | strftime('%d/%m/%Y %H:%M:%S', datetime(message.date + 978307200, 'unixepoch', 'localtime')) AS human_date, 201 | CASE WHEN message.cache_has_attachments THEN '' || message.text ELSE message.text END AS text 202 | FROM message 203 | LEFT JOIN handle ON message.handle_id=handle.ROWID 204 | NATURAL LEFT JOIN groupchat_membership 205 | ) 206 | SELECT unsorted_messages.recipient, sender, human_date, text 207 | FROM unsorted_messages 208 | NATURAL LEFT JOIN ( 209 | SELECT recipient, max(date) AS latest_date FROM unsorted_messages GROUP BY recipient 210 | ) 211 | ORDER BY latest_date DESC, date ASC; 212 | ''') 213 | result = {} 214 | recipients = [] 215 | for recipient, g in itertools.groupby(sql_results, lambda row: row[0]): 216 | recipients.append(recipient) 217 | result[recipient] = map(lambda row: (row[1:]), g) 218 | 219 | template_values = { 'result': result, 220 | 'recipients': recipients, 221 | 'pagename': 'iMessage Dump of ' + db_filename } 222 | 223 | with open(db_filename + ".html", 'wb') as f: 224 | f.write(jinja2.Environment(extensions=['jinja2.ext.autoescape'], trim_blocks=True, lstrip_blocks=True) 225 | .from_string(template) 226 | .render(template_values) 227 | .encode('utf-8')) 228 | conn.close() 229 | 230 | if __name__ == "__main__": 231 | if len(sys.argv) < 2: 232 | sys.exit('usage: %s db1 [db2 ...]' % sys.argv[0]) 233 | for db in sys.argv[1:]: 234 | render_imsg_db(db) 235 | --------------------------------------------------------------------------------