Clean up commands
This commit is contained in:
parent
c4610ca808
commit
6ff82c66aa
|
@ -16,37 +16,29 @@ struct Download: ParsableCommand {
|
||||||
@Option(name: [.short, .long], help: "The bundle identifier of the target iOS app.")
|
@Option(name: [.short, .long], help: "The bundle identifier of the target iOS app.")
|
||||||
private var bundleIdentifier: String
|
private var bundleIdentifier: String
|
||||||
|
|
||||||
@Option(name: [.short, .long], help: "The email address for the Apple ID.")
|
@Option(name: [.short, .customLong("email")], help: "The email address for the Apple ID.")
|
||||||
private var email: String?
|
private var emailArgument: String?
|
||||||
|
|
||||||
@Option(name: [.short, .long], help: "The password for the Apple ID.")
|
@Option(name: [.short, .customLong("password")], help: "The password for the Apple ID.")
|
||||||
private var password: String?
|
private var passwordArgument: String?
|
||||||
|
|
||||||
@Option
|
@Option
|
||||||
private var logLevel: LogLevel = .info
|
private var logLevel: LogLevel = .info
|
||||||
|
|
||||||
|
lazy var logger = ConsoleLogger(level: logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Download {
|
extension Download {
|
||||||
func run() throws {
|
mutating func app(with bundleIdentifier: String) -> iTunesResponse.Result {
|
||||||
let logger = ConsoleLogger(level: logLevel)
|
|
||||||
|
|
||||||
logger.log("Creating HTTP client...", level: .debug)
|
logger.log("Creating HTTP client...", level: .debug)
|
||||||
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
||||||
|
|
||||||
logger.log("Creating iTunes client...", level: .debug)
|
logger.log("Creating iTunes client...", level: .debug)
|
||||||
let itunesClient = iTunesClient(httpClient: httpClient)
|
let itunesClient = iTunesClient(httpClient: httpClient)
|
||||||
|
|
||||||
logger.log("Creating App Store client...", level: .debug)
|
|
||||||
let storeClient = StoreClient(httpClient: httpClient)
|
|
||||||
|
|
||||||
logger.log("Creating download client...", level: .debug)
|
|
||||||
let downloadClient = HTTPDownloadClient()
|
|
||||||
|
|
||||||
logger.log("Querying the iTunes store for '\(bundleIdentifier)'...", level: .info)
|
|
||||||
let app: iTunesResponse.Result
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
app = try itunesClient.lookup(bundleIdentifier: bundleIdentifier)
|
logger.log("Querying the iTunes Store for '\(bundleIdentifier)'...", level: .info)
|
||||||
|
return try itunesClient.lookup(bundleIdentifier: bundleIdentifier)
|
||||||
} catch {
|
} catch {
|
||||||
logger.log("\(error)", level: .debug)
|
logger.log("\(error)", level: .debug)
|
||||||
|
|
||||||
|
@ -59,45 +51,51 @@ extension Download {
|
||||||
|
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
logger.log("Found app: \(app.name) (\(app.version)).", level: .debug)
|
}
|
||||||
|
|
||||||
let email: String
|
mutating func email() -> String {
|
||||||
let password: String
|
if let email = emailArgument {
|
||||||
|
return email
|
||||||
if let cliEmail = self.email {
|
} else if let email = ProcessInfo.processInfo.environment["IPATOOL_EMAIL"] {
|
||||||
email = cliEmail
|
return email
|
||||||
} else if let envEmail = ProcessInfo.processInfo.environment["IPATOOL_EMAIL"] {
|
} else if let email = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter Apple ID email: ", level: .warning)))) {
|
||||||
email = envEmail
|
return email
|
||||||
} else if let inputEmail = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter Apple ID email: ", level: .warning)))) {
|
|
||||||
email = inputEmail
|
|
||||||
} else {
|
} else {
|
||||||
logger.log("An Apple ID email address is required.", level: .error)
|
logger.log("An Apple ID email address is required.", level: .error)
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let cliPassword = self.password {
|
mutating func password() -> String {
|
||||||
password = cliPassword
|
if let password = passwordArgument {
|
||||||
} else if let envPassword = ProcessInfo.processInfo.environment["IPATOOL_PASSWORD"] {
|
return password
|
||||||
password = envPassword
|
} else if let password = ProcessInfo.processInfo.environment["IPATOOL_PASSWORD"] {
|
||||||
} else if let inputPassword = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter Apple ID password: ", level: .warning)))) {
|
return password
|
||||||
password = inputPassword
|
} else if let password = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter Apple ID password: ", level: .warning)))) {
|
||||||
|
return password
|
||||||
} else {
|
} else {
|
||||||
logger.log("An Apple ID password is required.", level: .error)
|
logger.log("An Apple ID password is required.", level: .error)
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let account: StoreResponse.Account
|
mutating func authenticate(email: String, password: String) -> StoreResponse.Account {
|
||||||
|
logger.log("Creating HTTP client...", level: .debug)
|
||||||
|
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
||||||
|
|
||||||
|
logger.log("Creating App Store client...", level: .debug)
|
||||||
|
let storeClient = StoreClient(httpClient: httpClient)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
logger.log("Authenticating with the App Store...", level: .info)
|
logger.log("Authenticating with the App Store...", level: .info)
|
||||||
account = try storeClient.authenticate(email: email, password: password)
|
return try storeClient.authenticate(email: email, password: password)
|
||||||
} catch {
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case StoreResponse.Error.codeRequired:
|
case StoreResponse.Error.codeRequired:
|
||||||
let code = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter 2FA code: ", level: .warning))))
|
let code = String(validatingUTF8: UnsafePointer<CChar>(getpass(logger.compile("Enter 2FA code: ", level: .warning))))
|
||||||
|
|
||||||
do {
|
do {
|
||||||
account = try storeClient.authenticate(email: email, password: password, code: code)
|
return try storeClient.authenticate(email: email, password: password, code: code)
|
||||||
} catch {
|
} catch {
|
||||||
logger.log("\(error)", level: .debug)
|
logger.log("\(error)", level: .debug)
|
||||||
|
|
||||||
|
@ -135,13 +133,19 @@ extension Download {
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.log("Authenticated as '\(account.firstName) \(account.lastName)'.", level: .info)
|
|
||||||
|
|
||||||
logger.log("Requesting a signed copy of '\(app.identifier)' from the App Store...", level: .info)
|
}
|
||||||
let item: StoreResponse.Item
|
|
||||||
|
mutating func item(from app: iTunesResponse.Result, account: StoreResponse.Account) -> StoreResponse.Item {
|
||||||
|
logger.log("Creating HTTP client...", level: .debug)
|
||||||
|
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
||||||
|
|
||||||
|
logger.log("Creating App Store client...", level: .debug)
|
||||||
|
let storeClient = StoreClient(httpClient: httpClient)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
item = try storeClient.item(identifier: "\(app.identifier)", directoryServicesIdentifier: account.directoryServicesIdentifier)
|
logger.log("Requesting a signed copy of '\(app.identifier)' from the App Store...", level: .info)
|
||||||
|
return try storeClient.item(identifier: "\(app.identifier)", directoryServicesIdentifier: account.directoryServicesIdentifier)
|
||||||
} catch {
|
} catch {
|
||||||
logger.log("\(error)", level: .debug)
|
logger.log("\(error)", level: .debug)
|
||||||
|
|
||||||
|
@ -158,28 +162,32 @@ extension Download {
|
||||||
|
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.log("Received a response of the signed copy: \(item.md5).", level: .debug)
|
mutating func download(item: StoreResponse.Item, to targetURL: URL) {
|
||||||
|
logger.log("Creating download client...", level: .debug)
|
||||||
logger.log("Creating signature client...", level: .debug)
|
let downloadClient = HTTPDownloadClient()
|
||||||
let path = FileManager.default.currentDirectoryPath
|
|
||||||
.appending("/\(bundleIdentifier)_\(app.identifier)_v\(app.version)_\(Int.random(in: 100...999))")
|
|
||||||
.appending(".ipa")
|
|
||||||
|
|
||||||
logger.log("Output path: \(path).", level: .debug)
|
|
||||||
let signatureClient = SignatureClient(fileManager: .default, filePath: path)
|
|
||||||
|
|
||||||
|
do {
|
||||||
logger.log("Downloading app package...", level: .info)
|
logger.log("Downloading app package...", level: .info)
|
||||||
try downloadClient.download(from: item.url, to: URL(fileURLWithPath: path)) { progress in
|
try downloadClient.download(from: item.url, to: targetURL) { [logger] progress in
|
||||||
logger.log("Downloading app package... [\(Int((progress * 100).rounded()))%]",
|
logger.log("Downloading app package... [\(Int((progress * 100).rounded()))%]",
|
||||||
prefix: "\u{1B}[1A\u{1B}[K",
|
prefix: "\u{1B}[1A\u{1B}[K",
|
||||||
level: .info)
|
level: .info)
|
||||||
}
|
}
|
||||||
logger.log("Saved app package to \(URL(fileURLWithPath: path).lastPathComponent).", level: .info)
|
} catch {
|
||||||
|
logger.log("\(error)", level: .debug)
|
||||||
|
logger.log("An error has occurred while downloading the app package.", level: .error)
|
||||||
|
_exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.log("Applying patches...", level: .info)
|
mutating func applyPatches(item: StoreResponse.Item, email: String, path: String) {
|
||||||
|
logger.log("Creating signature client...", level: .debug)
|
||||||
|
let signatureClient = SignatureClient(fileManager: .default, filePath: path)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
logger.log("Applying patches...", level: .info)
|
||||||
try signatureClient.appendMetadata(item: item, email: email)
|
try signatureClient.appendMetadata(item: item, email: email)
|
||||||
try signatureClient.appendSignature(item: item)
|
try signatureClient.appendSignature(item: item)
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -187,7 +195,39 @@ extension Download {
|
||||||
logger.log("Failed to apply patches. The ipa file will be left incomplete.", level: .error)
|
logger.log("Failed to apply patches. The ipa file will be left incomplete.", level: .error)
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
// Query for app
|
||||||
|
let app: iTunesResponse.Result = app(with: bundleIdentifier)
|
||||||
|
logger.log("Found app: \(app.name) (\(app.version)).", level: .debug)
|
||||||
|
|
||||||
|
// Get Apple ID email
|
||||||
|
let email: String = email()
|
||||||
|
|
||||||
|
// Get Apple ID password
|
||||||
|
let password: String = password()
|
||||||
|
|
||||||
|
// Authenticate with the App Store
|
||||||
|
let account: StoreResponse.Account = authenticate(email: email, password: password)
|
||||||
|
logger.log("Authenticated as '\(account.firstName) \(account.lastName)'.", level: .info)
|
||||||
|
|
||||||
|
// Query for store item
|
||||||
|
let item: StoreResponse.Item = item(from: app, account: account)
|
||||||
|
logger.log("Received a response of the signed copy: \(item.md5).", level: .debug)
|
||||||
|
|
||||||
|
// Generate file name
|
||||||
|
let path = FileManager.default.currentDirectoryPath
|
||||||
|
.appending("/\(bundleIdentifier)_\(app.identifier)_v\(app.version)_\(Int.random(in: 100...999))")
|
||||||
|
.appending(".ipa")
|
||||||
|
logger.log("Output path: \(path).", level: .debug)
|
||||||
|
|
||||||
|
// Download app package
|
||||||
|
download(item: item, to: URL(fileURLWithPath: path))
|
||||||
|
logger.log("Saved app package to \(URL(fileURLWithPath: path).lastPathComponent).", level: .info)
|
||||||
|
|
||||||
|
// Apply patches
|
||||||
|
applyPatches(item: item, email: email, path: path)
|
||||||
logger.log("Done.", level: .info)
|
logger.log("Done.", level: .info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,12 @@ struct Search: ParsableCommand {
|
||||||
|
|
||||||
@Option
|
@Option
|
||||||
private var logLevel: LogLevel = .info
|
private var logLevel: LogLevel = .info
|
||||||
|
|
||||||
|
lazy var logger = ConsoleLogger(level: logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Search {
|
extension Search {
|
||||||
func run() throws {
|
mutating func results(with term: String) -> [iTunesResponse.Result] {
|
||||||
let logger = ConsoleLogger(level: logLevel)
|
|
||||||
|
|
||||||
logger.log("Creating HTTP client...", level: .debug)
|
logger.log("Creating HTTP client...", level: .debug)
|
||||||
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
let httpClient = HTTPClient(urlSession: URLSession.shared)
|
||||||
|
|
||||||
|
@ -42,16 +42,24 @@ extension Search {
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = results
|
return results
|
||||||
.enumerated()
|
|
||||||
.map({ "\($0 + 1). \($1.name): \($1.bundleIdentifier) (\($1.version))." })
|
|
||||||
.joined(separator: "\n")
|
|
||||||
|
|
||||||
logger.log("Found \(results.count) \(results.count == 1 ? "result" : "results"):\n\(output)", level: .info)
|
|
||||||
} catch {
|
} catch {
|
||||||
logger.log("\(error)", level: .debug)
|
logger.log("\(error)", level: .debug)
|
||||||
logger.log("An unknown error has occurred.", level: .error)
|
logger.log("An unknown error has occurred.", level: .error)
|
||||||
_exit(1)
|
_exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func run() throws {
|
||||||
|
// Search the iTunes store
|
||||||
|
let results = results(with: term)
|
||||||
|
|
||||||
|
// Compile output
|
||||||
|
let output = results
|
||||||
|
.enumerated()
|
||||||
|
.map({ "\($0 + 1). \($1.name): \($1.bundleIdentifier) (\($1.version))." })
|
||||||
|
.joined(separator: "\n")
|
||||||
|
|
||||||
|
logger.log("Found \(results.count) \(results.count == 1 ? "result" : "results"):\n\(output)", level: .info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,15 +57,15 @@
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "download -b com.apple.TestFlight"
|
argument = "download -b com.apple.TestFlight"
|
||||||
isEnabled = "YES">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "search --help"
|
argument = "search --help"
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "search TestFlight --limit 1"
|
argument = "search TestFlight --limit 5"
|
||||||
isEnabled = "NO">
|
isEnabled = "YES">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
</CommandLineArguments>
|
</CommandLineArguments>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
|
|
Loading…
Reference in New Issue