address_book.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. const log = require('../../util/log')
  4. const apple_timestamp = require('../../util/apple_timestamp')
  5. module.exports = {
  6. version: 4,
  7. name: 'phone.address_book',
  8. description: `List all address book records contained in the backup.`,
  9. requiresBackup: true,
  10. // Run on a v3 lib / backup object.
  11. run (lib, { backup, extract }) {
  12. return new Promise(async (resolve, reject) => {
  13. try {
  14. let items = await getAddressBook(backup, extract)
  15. resolve(items)
  16. } catch (e) {
  17. reject(e)
  18. }
  19. })
  20. },
  21. // Manifest fields.
  22. output: {
  23. id: el => el.ROWID,
  24. first: el => el.First || null,
  25. last: el => el.Last || null,
  26. organization: el => el.organization || null,
  27. phoneWork: el => el.phone_work || null,
  28. phoneMobile: el => el.phone_mobile || null,
  29. phoneHome: el => el.phone_home || null,
  30. email: el => el.email || null,
  31. createdDate: el => el.created_date || null,
  32. note: el => el.note || null,
  33. picture: el => !!el.profile_picture,
  34. picture_file: el => el.profile_picture_file || null,
  35. // picture_base64: el => el.profile_picture,
  36. services: el => el.services
  37. }
  38. }
  39. function getAddressBook (backup, extract) {
  40. return new Promise((resolve, reject) => {
  41. backup.openDatabase(backup.getFileID('Library/AddressBook/AddressBook.sqlitedb'))
  42. .then(db => {
  43. // Query basic Address Book fields
  44. const query = `
  45. select ABPerson.ROWID
  46. , ABPerson.first
  47. , ABPerson.middle
  48. , ABPerson.last
  49. , ABPerson.Organization as organization
  50. , ABPerson.Department as department
  51. , ABPerson.Birthday as birthday
  52. , ABPerson.JobTitle as jobtitle
  53. , ${apple_timestamp.parse('ABPerson.CreationDate')} as created_date
  54. , ${apple_timestamp.parse('ABPerson.ModificationDate')} as updated_date
  55. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
  56. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
  57. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
  58. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
  59. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'street')) as address
  60. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'city')) as city
  61. , ABPerson.Note as note
  62. from ABPerson
  63. order by ABPerson.ROWID
  64. `
  65. db.all(query, async function (err, rows) {
  66. if (err) reject(err)
  67. const iterateElements = (elements, index, callback) => {
  68. if (index === elements.length) { return callback() }
  69. // do parse call with element
  70. let ele = elements[index]
  71. // Query username and profile links for other services (facebook etc)
  72. const query = `
  73. select (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'PROFILE')) as google_profile
  74. , (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'profile')) as google_profile1
  75. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'iCloud')) as icloud
  76. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'service')) as service
  77. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'username')) as username
  78. , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'url')) as url
  79. from ABPerson
  80. where ABPerson.ROWID = ${ele.ROWID}
  81. order by ABPerson.ROWID
  82. `
  83. db.all(query, async function (err, rows1) {
  84. if (err) return reject(err)
  85. rows1[0].google_profile = rows1[0].google_profile || rows1[0].google_profile1
  86. delete rows1[0].google_profile1
  87. ele.services = rows1[0]
  88. backup.openDatabase(backup.getFileID('Library/AddressBook/AddressBookImages.sqlitedb'))
  89. .then(imageDB => {
  90. // Query profile picture extraction from /Library/AddressBook/AddressBookImages.sqlitedb
  91. const query = `
  92. select data
  93. from ABFullSizeImage
  94. where ABFullSizeImage.record_id = ${ele.ROWID}
  95. `
  96. imageDB.get(query, async function (err, row) {
  97. if (err) return reject(err)
  98. ele.profile_picture = null
  99. if (row) {
  100. ele.profile_picture = (row.data || '').toString('base64')
  101. if (extract && row.data) {
  102. fs.ensureDir(path.dirname(extract)).then( (err) => {
  103. var outFile = path.join(extract, `profile_picture_${ele.ROWID}.jpg`)
  104. // Log the export
  105. log.verbose('extract', outFile)
  106. fs.writeFile(outFile, row.data, (err) => {
  107. if (err) console.log(err);
  108. })
  109. ele.profile_picture_file = outFile
  110. })
  111. }
  112. }
  113. iterateElements(elements, index + 1, callback)
  114. })
  115. })
  116. .catch(reject)
  117. })
  118. }
  119. iterateElements(rows, 0, () => {
  120. resolve(rows)
  121. })
  122. })
  123. })
  124. .catch(reject)
  125. })
  126. }
  127. function extractProfilePictures (items, extractDest) {
  128. // Ensure output dir exists
  129. fs.ensureDir(path.dirname(extractDest)).then( (err) => {
  130. if (err) console.log(err)
  131. for (let item of items) {
  132. if (item.profile_picture) {
  133. var outFile = path.join(extractDest, `profile_picture_${item.ROWID}.jpg`)
  134. // Log the export
  135. log.verbose('extract', outFile)
  136. fs.writeFile(outFile, Buffer.from(item.profile_picture, 'base64'), (err) => {
  137. if (err) console.log(err);
  138. })
  139. item.profile_picture_file = outFile
  140. }
  141. }
  142. })
  143. }