cli.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #!/usr/bin/env node
  2. const program = require('commander')
  3. const chalk = require('chalk')
  4. const log = require('./util/log')
  5. const report = require('./reports')
  6. const matcher = require('./util/matcher')
  7. const Group = report.Group
  8. const core = require('./index')
  9. const packageJSON = require('../package.json')
  10. const { runSingleReport, runSwitchedReport } = require('./util/report_runner')
  11. var formatters = {
  12. 'json': require('./formatters/json'),
  13. 'table': require('./formatters/table'),
  14. 'raw': require('./formatters/raw-json'),
  15. 'raw-json': require('./formatters/raw-json'),
  16. 'csv': require('./formatters/csv'),
  17. 'raw-csv': require('./formatters/raw-csv')
  18. }
  19. process.on('unhandledRejection', (e) => {
  20. console.log('[cli.js] unhandled rejection', e)
  21. process.exit(1)
  22. })
  23. program
  24. .version(packageJSON.version)
  25. .option('-l, --list', 'List Backups')
  26. .option(`-b, --backup <backup>`, 'Backup ID')
  27. .option(`-d, --dir <directory>`, `Backup Directory (default: ${core.base})`)
  28. .option('-r, --report <report_type>', 'Select a report type. see below for a full list.')
  29. .option('-i, --id <id>', 'Specify an ID for filtering certain reports')
  30. .option('-f, --formatter <type>', 'Specify output format. default: table')
  31. .option(`-e, --extract <dir>`, 'Extract data for commands. supported by: voicemail-files, manifest, addressbook')
  32. .option('-o, --output <path>', 'Specify an output directory for files to be written to.')
  33. .option(`-v, --verbose`, 'Verbose debugging output')
  34. .option(` --plugins <plugins>`, 'List of pluging modules to use')
  35. .option(` --filter <filter>`, 'Filter output fo r individual reports. See the README for usage.')
  36. .option(' --join-reports', 'Join JSON reports together. (available for -f json or -f raw only!)')
  37. .option(` --no-color`, 'Disable colorized output')
  38. .option(` --dump`, 'alias for "--formatter raw"')
  39. .option(` --quiet`, 'quiet all messages, except for errors and raw output')
  40. .option(` --available`, 'output a list of available reports')
  41. program.on('--help', function () {
  42. console.log('')
  43. console.log(`Version: ${packageJSON.version}`)
  44. console.log('')
  45. console.log('Run ibackuptool --available for a listing of report types.')
  46. console.log('')
  47. console.log("If you're interested to know how this works, check out my post:")
  48. console.log('https://www.richinfante.com/2017/3/16/reverse-engineering-the-ios-backup')
  49. console.log('')
  50. console.log('Issue tracker:')
  51. console.log('https://github.com/richinfante/iphonebackuptools/issues')
  52. console.log('')
  53. })
  54. function printReportList () {
  55. // Print a report group and recursively print children
  56. function printGroup (group, i, pn) {
  57. i = i || 0
  58. pn = pn || ''
  59. for (let [name, report] of Object.entries(group)) {
  60. // Ignore groups
  61. if (name === '__group') { continue }
  62. if (report instanceof Group || report.__group === true) {
  63. // console.log(`${' '.repeat(i * 2)}- ${pn}${name}`.padStart(i * 2))
  64. printGroup(report, i + 1, `${pn}${name}.`)
  65. } else {
  66. console.log(`- ${chalk.green(pn + name)}${report.deprecated ? chalk.gray(' [deprecated]') : ''}: ${report.description} `)
  67. }
  68. }
  69. }
  70. printGroup(core.modules)
  71. }
  72. process.on('unhandledRejection', (e) => {
  73. console.log('[index.js] unhandled rejection', e)
  74. process.exit(1)
  75. })
  76. // If we're the main module, run some things.
  77. if (require.main === module) {
  78. init()
  79. }
  80. // Initialize the tool.
  81. function init () {
  82. program.parse(process.argv)
  83. if (program.available) {
  84. printReportList()
  85. process.exit(0)
  86. }
  87. // Set the log level
  88. log.setVerbose(program.quiet ? 0 : (program.verbose ? 2 : 1))
  89. // Parse plugins
  90. program.plugins = program.plugins || process.env.IBACKUPTOOL_PLUGINS || ''
  91. program.plugins = program.plugins.split(',')
  92. .map(name => name.trim())
  93. .filter(name => name !== '')
  94. .map(path => require(path))
  95. // Register witht the core module
  96. core.registerModule(program.plugins)
  97. log.verbose('Plugins:', program.plugins)
  98. // Save the formatter
  99. program.formatter = formatters[program.formatter] || formatters.table
  100. // Legacy support for `--dump` flag.
  101. if (program.dump) {
  102. program.formatter = formatters.raw
  103. }
  104. // Disable color for non-ttys.
  105. if (!process.stdout.isTTY) { program.color = false }
  106. // Find the base if it is set
  107. if (program.dir) {
  108. core.base = program.dir
  109. }
  110. log.verbose('Base Directory:', core.base)
  111. main().then(() => {})
  112. }
  113. /**
  114. * Main CLI function
  115. */
  116. async function main () {
  117. // Legacy support for --list (-l) flag
  118. if (program.list) {
  119. program.report = 'backups.list'
  120. }
  121. log.verbose('Top-level modules', Object.keys(core.modules))
  122. if (program.report) {
  123. var reportContents = []
  124. // Turn the report argument into an array of report type names
  125. var selectedTypes = program.report
  126. .split(',')
  127. .map(el => el.trim())
  128. .filter(el => el !== '')
  129. let selectedReports = []
  130. // Match an array of reports.
  131. for (let query of selectedTypes) {
  132. selectedReports = [
  133. ...selectedReports,
  134. ...matcher(core.modules, query, (el) => !(el instanceof Group || el.__group === true))
  135. ]
  136. }
  137. let set = new Set(selectedReports)
  138. log.verbose('selected set', set)
  139. // If the set's size is 0, throw an error.
  140. if (set.size === 0) {
  141. log.error(`Couldn't run reports specified by: '${program.report}'.`)
  142. log.error(`No matching reports were found.`)
  143. process.exit(1)
  144. }
  145. // Iterate over each item in the set.
  146. for (let report of set.values()) {
  147. if (report.version && report.version >= 3) {
  148. try {
  149. log.begin('run', report.name)
  150. // Create some parameters to send by default.
  151. let params = {
  152. backup: program.backup,
  153. extract: program.extract,
  154. filter: program.filter,
  155. id: program.id,
  156. raw: !!program.formatter.isRaw
  157. }
  158. // Run a v3 report.
  159. let contents = await core.runReport(report, params)
  160. // Possibly symbolicate
  161. contents = await core.compileReport(report, contents, params)
  162. // Format the v3 report's result.
  163. let formattedContent = program.formatter.format(contents, {
  164. program
  165. })
  166. // Push onto the list to be compiled.
  167. reportContents.push({
  168. name: report.name,
  169. contents: formattedContent
  170. })
  171. log.end()
  172. } catch (e) {
  173. log.end()
  174. log.error(`Couldn't run '${report.name}'.`)
  175. log.error(e)
  176. }
  177. } else {
  178. // Older reports still work for file and screen output
  179. // They just may not be as compatible
  180. // They cannot be called from the module's api.
  181. if (selectedReports.length > 1 && !report.usesPromises) {
  182. log.warning('the report', report.name, 'does not utilize promises.')
  183. log.warning('this may not work')
  184. }
  185. log.begin('run', report.name)
  186. // Check if there's a backup specified and one is required.
  187. if (report.requiresBackup) {
  188. if (!program.backup) {
  189. log.error('use -b or --backup <id> to specify backup.')
  190. process.exit(1)
  191. }
  192. }
  193. try {
  194. if (report.func) {
  195. let contents = await runSingleReport(report, program)
  196. if (contents == null) { log.end(); continue }
  197. reportContents.push({
  198. name: report.name,
  199. contents: contents
  200. })
  201. } else if (report.functions) {
  202. let contents = await runSwitchedReport(report, program)
  203. if (contents == null) { log.end(); continue }
  204. reportContents.push({
  205. name: report.name,
  206. contents: contents
  207. })
  208. }
  209. } catch (e) {
  210. log.error(`Couldn't run '${report.name}'.`)
  211. log.error(e)
  212. }
  213. log.end()
  214. }
  215. }
  216. program.formatter.finalReport(reportContents, program)
  217. } else {
  218. program.outputHelp()
  219. }
  220. }