conversations.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. const bplist = require('bplist-parser')
  2. const fileHash = require('../../util/backup_filehash')
  3. const log = require('../../util/log')
  4. const SMS_DB = fileHash('Library/SMS/sms.db')
  5. module.exports = {
  6. version: 4,
  7. name: 'messages.conversations',
  8. description: `List all SMS and iMessage conversations`,
  9. requiresBackup: true,
  10. // Available fields.
  11. output: {
  12. id: el => el.ROWID,
  13. date: el => el.XFORMATTEDDATESTRING || '??',
  14. service: el => el.service_name + '',
  15. chatName: el => el.chat_identifier + '',
  16. displayName: el => el.display_name + ''
  17. },
  18. // Run on a v3 lib / backup object.
  19. run (lib, { backup }) {
  20. return getConversations(backup)
  21. }
  22. }
  23. function getConversationsiOS9 (backup) {
  24. return new Promise((resolve, reject) => {
  25. backup.openDatabase(SMS_DB)
  26. .then(db => {
  27. db.all(`SELECT * FROM chat ORDER BY ROWID ASC`, async function (err, rows) {
  28. if (err) return reject(err)
  29. rows = rows || []
  30. // We need to do some manual parsing of these records.
  31. // The timestamp information is stored in a binary blob named `properties`
  32. // Which is formatted as a binary PLIST.
  33. for (var el of rows) {
  34. if (el.properties) el.properties = bplist.parseBuffer(el.properties)[0]
  35. // Interestingly, some of these do not have dates attached.
  36. if (el.properties) {
  37. el.date = new Date(el.properties.CKChatWatermarkTime * 1000)
  38. } else {
  39. el.date = new Date(0)
  40. }
  41. // Format as YY-MM-DD HH:MM:SS
  42. try {
  43. el.XFORMATTEDDATESTRING = el.date.toISOString()
  44. .split('T')
  45. .join(' ')
  46. .split('Z')
  47. .join(' ')
  48. .split('.')[0]
  49. .trim()
  50. } catch (e) {
  51. el.XFORMATTEDDATESTRING = ''
  52. }
  53. }
  54. // Sort by the date.
  55. rows = rows.sort(function (a, b) {
  56. return (a.date.getTime() || 0) - (b.date.getTime() || 0)
  57. })
  58. resolve(rows)
  59. })
  60. })
  61. .catch(reject)
  62. })
  63. }
  64. function getConversationsiOS10iOS11 (backup) {
  65. return new Promise((resolve, reject) => {
  66. backup.openDatabase(SMS_DB)
  67. .then(db => {
  68. db.all(`SELECT *, datetime(last_read_message_timestamp / 1000000000 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM chat ORDER BY last_read_message_timestamp ASC`, async function (err, rows) {
  69. if (err) return reject(err)
  70. rows = rows || []
  71. resolve(rows)
  72. })
  73. })
  74. .catch(reject)
  75. })
  76. }
  77. function getConversations (backup) {
  78. return new Promise(async (resolve, reject) => {
  79. try {
  80. let conversations = await getConversationsiOS10iOS11(backup)
  81. return resolve(conversations)
  82. } catch (e) {
  83. log.verbose('failed to read sms conversations as iOS10/11 format', e)
  84. }
  85. try {
  86. let conversations = await getConversationsiOS9(backup)
  87. return resolve(conversations)
  88. } catch (e) {
  89. log.verbose('failed to read sms conversations as iOS9 format', e)
  90. }
  91. reject(new Error('No suitable SMS database found. Use -v to see error informaton.'))
  92. })
  93. }