From 169af4b2eaa5488fe401810f032419c9fcf9a1ef Mon Sep 17 00:00:00 2001 From: Max Foxie Date: Sun, 7 Apr 2024 17:07:45 +0300 Subject: [PATCH] Add ability to download .ipa by the app ID (#263) * Add ability to download .ipa by the app ID * Update `download` command usage in README * Fix the linter warnings --- README.md | 10 ++++++---- cmd/download.go | 24 +++++++++++++++++------- pkg/appstore/appstore_download.go | 20 ++++++++++++++++---- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 55e17b6a..19830a1c 100644 --- a/README.md +++ b/README.md @@ -107,15 +107,17 @@ Usage: ipatool download [flags] Flags: - -b, --bundle-identifier string The bundle identifier of the target iOS app (required) + -i, --app-id int ID of the target iOS app (required) + -b, --bundle-identifier string The bundle identifier of the target iOS app (overrides the app ID) -h, --help help for download -o, --output string The destination path of the downloaded app package --purchase Obtain a license for the app if needed Global Flags: - --format format sets output format for command; can be 'text', 'json' (default text) - --non-interactive run in non-interactive session - --verbose enables verbose logs + --format format sets output format for command; can be 'text', 'json' (default text) + --keychain-passphrase string passphrase for unlocking keychain + --non-interactive run in non-interactive session + --verbose enables verbose logs ``` **Note:** the tool runs in interactive mode by default. Use the `--non-interactive` flag diff --git a/cmd/download.go b/cmd/download.go index 2ef6c8e0..b5fcccc3 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -16,6 +16,7 @@ func downloadCmd() *cobra.Command { var ( acquireLicense bool outputPath string + appID int64 bundleID string ) @@ -23,6 +24,10 @@ func downloadCmd() *cobra.Command { Use: "download", Short: "Download (encrypted) iOS app packages from the App Store", RunE: func(cmd *cobra.Command, args []string) error { + if appID == 0 && bundleID == "" { + return errors.New("either the app ID or the bundle identifier must be specified") + } + var lastErr error var acc appstore.Account @@ -43,13 +48,18 @@ func downloadCmd() *cobra.Command { acc = loginResult.Account } - lookupResult, err := dependencies.AppStore.Lookup(appstore.LookupInput{Account: acc, BundleID: bundleID}) - if err != nil { - return err + app := appstore.App{ID: appID} + if bundleID != "" { + lookupResult, err := dependencies.AppStore.Lookup(appstore.LookupInput{Account: acc, BundleID: bundleID}) + if err != nil { + return err + } + + app = lookupResult.App } if errors.Is(lastErr, appstore.ErrLicenseRequired) { - err := dependencies.AppStore.Purchase(appstore.PurchaseInput{Account: acc, App: lookupResult.App}) + err := dependencies.AppStore.Purchase(appstore.PurchaseInput{Account: acc, App: app}) if err != nil { return err } @@ -74,7 +84,7 @@ func downloadCmd() *cobra.Command { ) } - out, err := dependencies.AppStore.Download(appstore.DownloadInput{Account: acc, App: lookupResult.App, OutputPath: outputPath, Progress: progress}) + out, err := dependencies.AppStore.Download(appstore.DownloadInput{Account: acc, App: app, OutputPath: outputPath, Progress: progress}) if err != nil { return err } @@ -112,10 +122,10 @@ func downloadCmd() *cobra.Command { }, } - cmd.Flags().StringVarP(&bundleID, "bundle-identifier", "b", "", "The bundle identifier of the target iOS app (required)") + cmd.Flags().Int64VarP(&appID, "app-id", "i", 0, "ID of the target iOS app (required)") + cmd.Flags().StringVarP(&bundleID, "bundle-identifier", "b", "", "The bundle identifier of the target iOS app (overrides the app ID)") cmd.Flags().StringVarP(&outputPath, "output", "o", "", "The destination path of the downloaded app package") cmd.Flags().BoolVar(&acquireLicense, "purchase", false, "Obtain a license for the app if needed") - _ = cmd.MarkFlagRequired("bundle-identifier") return cmd } diff --git a/pkg/appstore/appstore_download.go b/pkg/appstore/appstore_download.go index 094dbab2..9a3d307e 100644 --- a/pkg/appstore/appstore_download.go +++ b/pkg/appstore/appstore_download.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" "github.com/majd/ipatool/v2/pkg/http" @@ -161,10 +162,21 @@ func (*appstore) downloadRequest(acc Account, app App, guid string) http.Request } func fileName(app App) string { - return fmt.Sprintf("%s_%d_%s.ipa", - app.BundleID, - app.ID, - app.Version) + var parts []string + + if app.BundleID != "" { + parts = append(parts, app.BundleID) + } + + if app.ID != 0 { + parts = append(parts, strconv.FormatInt(app.ID, 10)) + } + + if app.Version != "" { + parts = append(parts, app.Version) + } + + return fmt.Sprintf("%s.ipa", strings.Join(parts, "_")) } func (t *appstore) resolveDestinationPath(app App, path string) (string, error) {