import Foundation enum StickerClonerAPIError: LocalizedError { case invalidResponse(Int) case decodingFailed(Error) var errorDescription: String? { switch self { case .invalidResponse(let code): return "Server returned status \(code)" case .decodingFailed(let error): return "Failed to decode response: \(error.localizedDescription)" } } } enum StickerClonerAPI { private static let baseURL = URL(string: "https://serve.uber.space/sticker-cloner")! private static let decoder: JSONDecoder = { let d = JSONDecoder() d.keyDecodingStrategy = .convertFromSnakeCase return d }() static func fetchStickerSet(name: String) async throws -> StickerSetResponse { let url = baseURL.appendingPathComponent("api/stickersets/\(name)") let (data, response) = try await URLSession.shared.data(from: url) guard let http = response as? HTTPURLResponse, 200..<300 ~= http.statusCode else { let code = (response as? HTTPURLResponse)?.statusCode ?? -1 throw StickerClonerAPIError.invalidResponse(code) } do { return try decoder.decode(StickerSetResponse.self, from: data) } catch { throw StickerClonerAPIError.decodingFailed(error) } } static func downloadSticker(from relativePath: String, to localURL: URL) async throws { let url = baseURL.appendingPathComponent(relativePath) let (tempURL, response) = try await URLSession.shared.download(from: url) guard let http = response as? HTTPURLResponse, 200..<300 ~= http.statusCode else { let code = (response as? HTTPURLResponse)?.statusCode ?? -1 throw StickerClonerAPIError.invalidResponse(code) } let directory = localURL.deletingLastPathComponent() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) if FileManager.default.fileExists(atPath: localURL.path) { try FileManager.default.removeItem(at: localURL) } try FileManager.default.moveItem(at: tempURL, to: localURL) } }