conversations.js 3.2 KB

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