67 lines
2.1 KiB
Swift
67 lines
2.1 KiB
Swift
//
|
|
// ITunesSearchService.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum ITunesSearchError: Error {
|
|
case invalidURL
|
|
case invalidResponse
|
|
}
|
|
|
|
enum ITunesSearchService {
|
|
private struct SearchResponse: Decodable {
|
|
let results: [ITunesAppResult]
|
|
}
|
|
|
|
private struct ITunesAppResult: Decodable {
|
|
let trackId: Int
|
|
let trackName: String
|
|
let artworkUrl100: String?
|
|
let artworkUrl512: String?
|
|
}
|
|
|
|
static func search(term: String, limit: Int = 25) async throws -> [AppCatalogItem] {
|
|
let trimmed = term.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return [] }
|
|
|
|
var components = URLComponents(string: "https://itunes.apple.com/search")
|
|
components?.queryItems = [
|
|
URLQueryItem(name: "term", value: trimmed),
|
|
URLQueryItem(name: "entity", value: "software"),
|
|
URLQueryItem(name: "country", value: "cn"),
|
|
URLQueryItem(name: "limit", value: "\(limit)")
|
|
]
|
|
guard let url = components?.url else { throw ITunesSearchError.invalidURL }
|
|
|
|
let (data, response) = try await URLSession.shared.data(from: url)
|
|
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
|
|
throw ITunesSearchError.invalidResponse
|
|
}
|
|
|
|
let decoded = try JSONDecoder().decode(SearchResponse.self, from: data)
|
|
return decoded.results.map { result in
|
|
let iconURL = result.artworkUrl512 ?? result.artworkUrl100
|
|
return AppCatalogItem(
|
|
id: "itunes:\(result.trackId)",
|
|
name: result.trackName,
|
|
icon: "",
|
|
keywords: [],
|
|
iconURL: iconURL
|
|
)
|
|
}
|
|
}
|
|
|
|
static func mergedResults(local: [AppCatalogItem], remote: [AppCatalogItem]) -> [AppCatalogItem] {
|
|
var seen = Set<String>()
|
|
var merged: [AppCatalogItem] = []
|
|
for item in local + remote {
|
|
guard !seen.contains(item.id) else { continue }
|
|
seen.insert(item.id)
|
|
merged.append(item)
|
|
}
|
|
return merged
|
|
}
|
|
}
|