address_book.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. iphone: el => el.iphone || null,
  31. email: el => el.email || null,
  32. createdDate: el => el.created_date || null,
  33. note: el => el.note || null,
  34. picture: el => !!el.profile_picture,
  35. picture_file: el => el.profile_picture_file || null,
  36. // picture_base64: el => el.profile_picture,
  37. services: el => el.services,
  38. address: el => el.address,
  39. city: el => el.city
  40. }
  41. }
  42. function getAddressBook (backup, extract) {
  43. return new Promise((resolve, reject) => {
  44. backup.openDatabase(backup.getFileID('Library/AddressBook/AddressBook.sqlitedb'))
  45. .then(db => {
  46. // Query basic Address Book fields
  47. const query = `
  48. select ABPerson.ROWID
  49. , ABPerson.first
  50. , ABPerson.middle
  51. , ABPerson.last
  52. , ABPerson.Organization as organization
  53. , ABPerson.Department as department
  54. , ABPerson.Birthday as birthday
  55. , ABPerson.JobTitle as jobtitle
  56. , ${apple_timestamp.parse('ABPerson.CreationDate')} as created_date
  57. , ${apple_timestamp.parse('ABPerson.ModificationDate')} as updated_date
  58. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
  59. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
  60. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
  61. , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'iPhone')) as iphone
  62. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
  63. , (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
  64. , (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
  65. , ABPerson.Note as note
  66. from ABPerson
  67. order by ABPerson.ROWID
  68. `
  69. db.all(query, async function (err, rows) {
  70. if (err) reject(err)
  71. const iterateElements = (elements, index, callback) => {
  72. if (index === elements.length) { return callback() }
  73. // do parse call with element
  74. let ele = elements[index]
  75. // Query username and profile links for other services (facebook etc)
  76. const query = `
  77. 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
  78. , (select value from ABMultiValue where property = 22 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'profile')) as google_profile1
  79. , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = 'iCloud')) as icloud
  80. , (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
  81. , (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
  82. , (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
  83. from ABPerson
  84. where ABPerson.ROWID = ${ele.ROWID}
  85. order by ABPerson.ROWID
  86. `
  87. db.all(query, async function (err, rows1) {
  88. if (err) return reject(err)
  89. rows1[0].google_profile = rows1[0].google_profile || rows1[0].google_profile1
  90. delete rows1[0].google_profile1
  91. ele.services = rows1[0]
  92. backup.openDatabase(backup.getFileID('Library/AddressBook/AddressBookImages.sqlitedb'))
  93. .then(imageDB => {
  94. // Query profile picture extraction from /Library/AddressBook/AddressBookImages.sqlitedb
  95. const query = `
  96. select data
  97. from ABFullSizeImage
  98. where ABFullSizeImage.record_id = ${ele.ROWID}
  99. `
  100. imageDB.get(query, async function (err, row) {
  101. if (err) return reject(err)
  102. ele.profile_picture = null
  103. if (row) {
  104. ele.profile_picture = (row.data || '').toString('base64')
  105. if (extract && row.data) {
  106. fs.ensureDir(path.dirname(extract)).then( (err) => {
  107. var outFile = path.join(extract, `profile_picture_${ele.ROWID}.jpg`)
  108. // Log the export
  109. log.verbose('extract', outFile)
  110. fs.writeFile(outFile, row.data, (err) => {
  111. if (err) console.log(err);
  112. })
  113. ele.profile_picture_file = outFile
  114. })
  115. }
  116. }
  117. iterateElements(elements, index + 1, callback)
  118. })
  119. })
  120. .catch(reject)
  121. })
  122. }
  123. iterateElements(rows, 0, () => {
  124. resolve(rows)
  125. })
  126. })
  127. })
  128. .catch(reject)
  129. })
  130. }
  131. function extractProfilePictures (items, extractDest) {
  132. // Ensure output dir exists
  133. fs.ensureDir(path.dirname(extractDest)).then( (err) => {
  134. if (err) console.log(err)
  135. for (let item of items) {
  136. if (item.profile_picture) {
  137. var outFile = path.join(extractDest, `profile_picture_${item.ROWID}.jpg`)
  138. // Log the export
  139. log.verbose('extract', outFile)
  140. fs.writeFile(outFile, Buffer.from(item.profile_picture, 'base64'), (err) => {
  141. if (err) console.log(err);
  142. })
  143. item.profile_picture_file = outFile
  144. }
  145. }
  146. })
  147. }