From ec2feb28f80d1e4667fe8b0ea489e4d65339934f Mon Sep 17 00:00:00 2001 From: Fuwn Date: Thu, 27 Feb 2025 16:46:41 -0800 Subject: feat: Development commit --- Sora/Data/Danbooru/DanbooruMediaAsset.swift | 3 + Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift | 7 ++ Sora/Data/Danbooru/DanbooruPost.swift | 77 ++++++++++++++++++++++ Sora/Data/Danbooru/DanbooruPostParser.swift | 52 +++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 Sora/Data/Danbooru/DanbooruMediaAsset.swift create mode 100644 Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift create mode 100644 Sora/Data/Danbooru/DanbooruPost.swift create mode 100644 Sora/Data/Danbooru/DanbooruPostParser.swift (limited to 'Sora/Data/Danbooru') diff --git a/Sora/Data/Danbooru/DanbooruMediaAsset.swift b/Sora/Data/Danbooru/DanbooruMediaAsset.swift new file mode 100644 index 0000000..3b34c2e --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruMediaAsset.swift @@ -0,0 +1,3 @@ +struct DanbooruMediaAsset: Decodable { + let variants: [DanbooruMediaAssetVariant] +} diff --git a/Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift b/Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift new file mode 100644 index 0000000..2151314 --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift @@ -0,0 +1,7 @@ +struct DanbooruMediaAssetVariant: Decodable { + let type: String + let url: String + let width: Int + let height: Int + let file_ext: String // swiftlint:disable:this identifier_name +} diff --git a/Sora/Data/Danbooru/DanbooruPost.swift b/Sora/Data/Danbooru/DanbooruPost.swift new file mode 100644 index 0000000..216816e --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruPost.swift @@ -0,0 +1,77 @@ +import Foundation + +struct DanbooruPost: Decodable { + let id: Int + let createdAt: Date + let uploaderId: Int + let score: Int + let source: String + let md5: String + let rating: String + let imageWidth: Int + let imageHeight: Int + let tagString: String + let parentId: Int? + let mediaAsset: DanbooruMediaAsset + let fileURL: String + let largeFileURL: String + let previewFileURL: String + let isDeleted: Bool + + enum CodingKeys: String, CodingKey { + case id = "id" + case createdAt = "created_at" + case uploaderId = "uploader_id" + case score = "score" + case source = "source" + case md5 = "md5" + case rating = "rating" + case imageWidth = "image_width" + case imageHeight = "image_height" + case tagString = "tag_string" + case parentId = "parent_id" + case mediaAsset = "media_asset" + case fileURL = "file_url" + case largeFileURL = "large_file_url" + case previewFileURL = "preview_file_url" + case isDeleted = "is_deleted" + } + + func toBooruPost() -> BooruPost? { + guard let fileURL = URL(string: fileURL), + let sampleURL = URL(string: largeFileURL), + let previewURL = URL(string: previewFileURL) + else { + return nil + } + + let previewVariant = mediaAsset.variants.first { $0.type == "180x180" } + let sampleVariant = + mediaAsset.variants.first { $0.type == "sample" } + ?? mediaAsset.variants.first { $0.type == "original" } + + return BooruPost( + id: String(id), + height: imageHeight, + score: String(score), + fileURL: fileURL, + parentID: parentId != nil ? String(parentId!) : "", + sampleURL: sampleURL, + sampleWidth: sampleVariant?.width ?? imageWidth, + sampleHeight: sampleVariant?.height ?? imageHeight, + previewURL: previewURL, + rating: BooruRating(rating), + tags: tagString.components(separatedBy: " ").filter { !$0.isEmpty }, + width: imageWidth, + change: nil, + md5: md5, + creatorID: String(uploaderId), + authorID: nil, + createdAt: createdAt, + status: isDeleted ? "deleted" : "active", + source: source, + previewWidth: previewVariant?.width ?? 180, + previewHeight: previewVariant?.height ?? 180 + ) + } +} diff --git a/Sora/Data/Danbooru/DanbooruPostParser.swift b/Sora/Data/Danbooru/DanbooruPostParser.swift new file mode 100644 index 0000000..fa62b5b --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruPostParser.swift @@ -0,0 +1,52 @@ +import Foundation + +class DanbooruPostParser { + private let data: Data + + init(data: Data) { + self.data = data + } + + func parse() -> [BooruPost] { + let decoder = JSONDecoder() + + decoder.dateDecodingStrategy = .custom { decoder in + self.parseDate( + try (try decoder.singleValueContainer()).decode(String.self) + ) ?? Date() + } + + do { + return try decoder.decode([DanbooruPost].self, from: data).compactMap { post in + post.toBooruPost() + } + } catch { + return [] + } + } + + private func parseDate(_ input: String) -> Date? { + let isoFormatter = ISO8601DateFormatter() + + isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + if let date = isoFormatter.date(from: input) { + return date + } + + let alternativeFormatter = DateFormatter() + + alternativeFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" + alternativeFormatter.locale = Locale(identifier: "en_US_POSIX") + + if let date = alternativeFormatter.date(from: input) { + return date + } + + if let timestamp = Double(input) { + return Date(timeIntervalSince1970: timestamp) + } + + return nil + } +} -- cgit v1.2.3