123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- const plist = require('../../util/plist')
- const fileHash = require('../../util/backup_filehash')
- const log = require('../../util/log')
- const apple_timestamp = require('../../util/apple_timestamp')
- const SMS_DB = fileHash('Library/SMS/sms.db')
- module.exports = {
- version: 4,
- name: 'messages.conversations',
- description: `List all SMS and iMessage conversations`,
- requiresBackup: true,
- // Available fields.
- output: {
- id: el => el.ROWID,
- date: el => el.XFORMATTEDDATESTRING || '??',
- service: el => el.service_name + '',
- chatName: el => el.chat_identifier + '',
- displayName: el => el.display_name + ''
- },
- // Run on a v3 lib / backup object.
- run (lib, { backup }) {
- return getConversations(backup)
- }
- }
- function getConversationsiOS9 (backup) {
- return new Promise((resolve, reject) => {
- backup.openDatabase(SMS_DB)
- .then(db => {
- db.all(`SELECT * FROM chat ORDER BY ROWID ASC`, async function (err, rows) {
- if (err) return reject(err)
- rows = rows || []
- // We need to do some manual parsing of these records.
- // The timestamp information is stored in a binary blob named `properties`
- // Which is formatted as a binary PLIST.
- for (var el of rows) {
- if (el.properties) el.properties = plist.parseBuffer(el.properties)
- // Interestingly, some of these do not have dates attached.
- if (el.properties) {
- el.date = new Date(el.properties.CKChatWatermarkTime * 1000)
- } else {
- el.date = new Date(0)
- }
- // Format as YY-MM-DD HH:MM:SS
- try {
- el.XFORMATTEDDATESTRING = el.date.toISOString()
- .split('T')
- .join(' ')
- .split('Z')
- .join(' ')
- .split('.')[0]
- .trim()
- } catch (e) {
- el.XFORMATTEDDATESTRING = ''
- }
- }
- // Sort by the date.
- rows = rows.sort(function (a, b) {
- return (a.date.getTime() || 0) - (b.date.getTime() || 0)
- })
- resolve(rows)
- })
- })
- .catch(reject)
- })
- }
- function getConversationsiOS10iOS11 (backup) {
- return new Promise((resolve, reject) => {
- backup.openDatabase(SMS_DB)
- .then(db => {
- db.all(`SELECT *, ${apple_timestamp.parse('last_read_message_timestamp')} AS XFORMATTEDDATESTRING FROM chat ORDER BY last_read_message_timestamp ASC`, async function (err, rows) {
- if (err) return reject(err)
- rows = rows || []
- resolve(rows)
- })
- })
- .catch(reject)
- })
- }
- function getConversations (backup) {
- return new Promise(async (resolve, reject) => {
- try {
- let conversations = await getConversationsiOS10iOS11(backup)
- return resolve(conversations)
- } catch (e) {
- log.verbose('failed to read sms conversations as iOS10/11 format', e)
- }
- try {
- let conversations = await getConversationsiOS9(backup)
- return resolve(conversations)
- } catch (e) {
- log.verbose('failed to read sms conversations as iOS9 format', e)
- }
- reject(new Error('No suitable SMS database found. Use -v to see error informaton.'))
- })
- }
|