Merge pull request #572 from rgoldberg/561-itunes-search

Fix iTunes Search API issues
This commit is contained in:
Ross Goldberg 2024-10-14 16:55:07 -04:00 committed by GitHub
commit bf627ccd79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 52 additions and 33 deletions

View file

@ -10,7 +10,7 @@ import ArgumentParser
extension Mas {
/// Opens app page on MAS Preview. Uses the iTunes Lookup API:
/// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#lookup
/// https://performance-partners.apple.com/search-api
struct Home: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Opens MAS Preview app page in a browser"

View file

@ -11,7 +11,7 @@ import Foundation
extension Mas {
/// Displays app details. Uses the iTunes Lookup API:
/// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#lookup
/// https://performance-partners.apple.com/search-api
struct Info: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Display app information from the Mac App Store"

View file

@ -13,7 +13,7 @@ private let masScheme = "macappstore"
extension Mas {
/// Opens app page in MAS app. Uses the iTunes Lookup API:
/// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#lookup
/// https://performance-partners.apple.com/search-api
struct Open: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Opens app page in AppStore.app"

View file

@ -10,7 +10,7 @@ import ArgumentParser
extension Mas {
/// Search the Mac App Store using the iTunes Search API:
/// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
/// https://performance-partners.apple.com/search-api
struct Search: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Search for apps from the Mac App Store"

View file

@ -10,7 +10,7 @@ import ArgumentParser
extension Mas {
/// Opens vendor's app page in a browser. Uses the iTunes Lookup API:
/// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#lookup
/// https://performance-partners.apple.com/search-api
struct Vendor: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Opens vendor's app page in a browser"

View file

@ -19,7 +19,7 @@ class MasStoreSearch: StoreSearch {
// into the App Store. Instead, we'll make an educated guess that it matches the currently
// selected locale in macOS. This obviously isn't always going to match, but it's probably
// better than passing no "country" at all to the iTunes Search API.
// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
// https://performance-partners.apple.com/search-api
private let country: String?
private let networkManager: NetworkManager
@ -40,7 +40,7 @@ class MasStoreSearch: StoreSearch {
func search(for appName: String) -> Promise<[SearchResult]> {
// Search for apps for compatible platforms, in order of preference.
// Macs with Apple Silicon can run iPad and iPhone apps.
var entities = [Entity.macSoftware]
var entities = [Entity.desktopSoftware]
if SysCtlSystemCommand.isAppleSilicon {
entities += [.iPadSoftware, .iPhoneSoftware]
}

View file

@ -16,53 +16,73 @@ protocol StoreSearch {
}
enum Entity: String {
case desktopSoftware
case macSoftware
case iPadSoftware
case iPhoneSoftware = "software"
}
private enum URLAction {
case lookup
case search
var queryItemName: String {
switch self {
case .lookup:
return "id"
case .search:
return "term"
}
}
}
// MARK: - Common methods
extension StoreSearch {
/// Builds the search URL for an app.
///
/// - Parameter appName: MAS app identifier.
/// - Returns: URL for the search service or nil if appName can't be encoded.
func searchURL(for appName: String, inCountry country: String?, ofEntity entity: Entity = .macSoftware) -> URL? {
guard var components = URLComponents(string: "https://itunes.apple.com/search") else {
return nil
}
components.queryItems = [
URLQueryItem(name: "media", value: "software"),
URLQueryItem(name: "entity", value: entity.rawValue),
URLQueryItem(name: "term", value: appName),
]
if let country {
components.queryItems!.append(URLQueryItem(name: "country", value: country))
}
return components.url
/// - Parameter searchTerm: term for which to search in MAS.
/// - Returns: URL for the search service or nil if searchTerm can't be encoded.
func searchURL(
for searchTerm: String,
inCountry country: String?,
ofEntity entity: Entity = .desktopSoftware
) -> URL? {
url(.search, searchTerm, inCountry: country, ofEntity: entity)
}
/// Builds the lookup URL for an app.
///
/// - Parameter appID: MAS app identifier.
/// - Returns: URL for the lookup service or nil if appID can't be encoded.
func lookupURL(forAppID appID: AppID, inCountry country: String?) -> URL? {
guard var components = URLComponents(string: "https://itunes.apple.com/lookup") else {
func lookupURL(
forAppID appID: AppID,
inCountry country: String?,
ofEntity entity: Entity = .desktopSoftware
) -> URL? {
url(.lookup, String(appID), inCountry: country, ofEntity: entity)
}
private func url(
_ action: URLAction,
_ queryItemValue: String,
inCountry country: String?,
ofEntity entity: Entity = .desktopSoftware
) -> URL? {
guard var components = URLComponents(string: "https://itunes.apple.com/\(action)") else {
return nil
}
components.queryItems = [
URLQueryItem(name: "id", value: "\(appID)"),
URLQueryItem(name: "entity", value: "desktopSoftware"),
URLQueryItem(name: "media", value: "software"),
URLQueryItem(name: "entity", value: entity.rawValue),
]
if let country {
components.queryItems!.append(URLQueryItem(name: "country", value: country))
}
components.queryItems!.append(URLQueryItem(name: action.queryItemName, value: queryItemValue))
return components.url
}
}

View file

@ -18,17 +18,16 @@ public class MasStoreSearchSpec: QuickSpec {
}
describe("url string") {
it("contains the app name") {
let appName = "myapp"
expect {
MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
MasStoreSearch().searchURL(for: "myapp", inCountry: "US")?.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)&country=US"
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp"
}
it("contains the encoded app name") {
expect {
MasStoreSearch().searchURL(for: "My App", inCountry: "US")?.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=macSoftware&term=My%20App&country=US"
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App"
}
}
describe("store") {