142 lines
4.3 KiB
Swift
142 lines
4.3 KiB
Swift
import Foundation
|
|
|
|
struct BookRemote: Codable, Identifiable, Hashable {
|
|
var id: String { isbn13 ?? isbn10 ?? title + (authors.first ?? "") }
|
|
|
|
let remoteId: String?
|
|
let title: String
|
|
let authors: [String]
|
|
let publishedYear: Int?
|
|
let isbn10: String?
|
|
let isbn13: String?
|
|
let coverImageUrl: URL?
|
|
let language: String?
|
|
let description: String?
|
|
let pageCount: Int?
|
|
let categories: [String]
|
|
let publisher: String?
|
|
let sourceLocale: String?
|
|
let readingStatus: ReadingStatus?
|
|
let readingProgress: Double?
|
|
|
|
init(
|
|
remoteId: String? = nil,
|
|
title: String,
|
|
authors: [String] = [],
|
|
publishedYear: Int? = nil,
|
|
isbn10: String? = nil,
|
|
isbn13: String? = nil,
|
|
coverImageUrl: URL? = nil,
|
|
language: String? = nil,
|
|
description: String? = nil,
|
|
pageCount: Int? = nil,
|
|
categories: [String] = [],
|
|
publisher: String? = nil,
|
|
sourceLocale: String? = nil,
|
|
readingStatus: ReadingStatus? = nil,
|
|
readingProgress: Double? = nil
|
|
) {
|
|
self.remoteId = remoteId
|
|
self.title = title
|
|
self.authors = authors
|
|
self.publishedYear = publishedYear
|
|
self.isbn10 = isbn10
|
|
self.isbn13 = isbn13
|
|
self.coverImageUrl = coverImageUrl
|
|
self.language = language
|
|
self.description = description
|
|
self.pageCount = pageCount
|
|
self.categories = categories
|
|
self.publisher = publisher
|
|
self.sourceLocale = sourceLocale
|
|
self.readingStatus = readingStatus
|
|
self.readingProgress = readingProgress
|
|
}
|
|
}
|
|
|
|
struct BookSearchResponse: Decodable {
|
|
let items: [BookRemote]
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
// Endpoint 1: /isbn returns { data: { tr: {...}, en: {...} } }
|
|
if let localeMap = try? container.decode([String: RawBook].self, forKey: .data) {
|
|
self.items = localeMap.map { locale, raw in raw.toBook(locale: locale) }
|
|
return
|
|
}
|
|
|
|
// Endpoint 2/3: /title and /filter returns { data: [{ locale, items: [...] }] }
|
|
if let groups = try? container.decode([LocaleGroup].self, forKey: .data) {
|
|
self.items = groups.flatMap { group in
|
|
group.items.map { $0.toBook(locale: group.locale) }
|
|
}
|
|
return
|
|
}
|
|
|
|
self.items = []
|
|
}
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case data
|
|
}
|
|
}
|
|
|
|
private struct LocaleGroup: Decodable {
|
|
let locale: String
|
|
let items: [RawBook]
|
|
}
|
|
|
|
private struct RawBook: Decodable {
|
|
let asin: String?
|
|
let title: String?
|
|
let authorName: String?
|
|
let author: String?
|
|
let isbn: String?
|
|
let thumbImage: String?
|
|
let image: String?
|
|
let date: String?
|
|
let publisher: String?
|
|
let page: Int?
|
|
let description: String?
|
|
let categories: [String]?
|
|
let locale: String?
|
|
|
|
func toBook(locale: String?) -> BookRemote {
|
|
let coverRaw = thumbImage ?? image
|
|
let authorsText = authorName ?? author ?? ""
|
|
let authors = authorsText
|
|
.replacingOccurrences(of: "[Yazar]", with: "")
|
|
.split(separator: ",")
|
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
|
|
let isbnClean = isbn?.filter { $0.isNumber || $0.uppercased() == "X" }
|
|
let isbn10 = isbnClean?.count == 10 ? isbnClean : nil
|
|
let isbn13 = isbnClean?.count == 13 ? isbnClean : nil
|
|
|
|
return BookRemote(
|
|
remoteId: asin,
|
|
title: title ?? "Untitled",
|
|
authors: authors,
|
|
publishedYear: Self.extractYear(from: date),
|
|
isbn10: isbn10,
|
|
isbn13: isbn13,
|
|
coverImageUrl: coverRaw.flatMap(URL.init(string:)),
|
|
language: locale,
|
|
description: description,
|
|
pageCount: page,
|
|
categories: categories ?? [],
|
|
publisher: publisher,
|
|
sourceLocale: locale
|
|
)
|
|
}
|
|
|
|
private static func extractYear(from value: String?) -> Int? {
|
|
guard let value else { return nil }
|
|
let digits = value.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
|
|
guard digits.count >= 4 else { return nil }
|
|
return Int(String(digits.prefix(4)))
|
|
}
|
|
}
|