└── 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 |
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 |
--------------------------------------------------------------------------------