diff options
| -rw-r--r-- | .swiftlint.yml | 1 | ||||
| -rw-r--r-- | Sora/Data/Booru/BooruManager.swift | 25 | ||||
| -rw-r--r-- | Sora/Data/Booru/BooruProvider.swift | 1 | ||||
| -rw-r--r-- | Sora/Data/Booru/BooruProviderFlavor.swift | 4 | ||||
| -rw-r--r-- | Sora/Data/Booru/Post/BooruPost.swift | 2 | ||||
| -rw-r--r-- | Sora/Data/Booru/Post/BooruPostXMLParser.swift | 1 | ||||
| -rw-r--r-- | Sora/Data/Danbooru/DanbooruMediaAsset.swift | 3 | ||||
| -rw-r--r-- | Sora/Data/Danbooru/DanbooruMediaAssetVariant.swift | 7 | ||||
| -rw-r--r-- | Sora/Data/Danbooru/DanbooruPost.swift | 77 | ||||
| -rw-r--r-- | Sora/Data/Danbooru/DanbooruPostParser.swift | 52 | ||||
| -rw-r--r-- | Sora/Views/Post/Details/PostDetailsImageView.swift | 3 |
11 files changed, 170 insertions, 6 deletions
diff --git a/.swiftlint.yml b/.swiftlint.yml index 0b28a12..430b4a1 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -24,4 +24,5 @@ disabled_rules: - trailing_comma - inert_defer - unused_capture_list + - type_contents_order diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift index 8cf1ac6..0fb0256 100644 --- a/Sora/Data/Booru/BooruManager.swift +++ b/Sora/Data/Booru/BooruManager.swift @@ -56,6 +56,9 @@ class BooruManager: ObservableObject { case .gelbooru: domain = "gelbooru.com" + + case .danbooru: + domain = "danbooru.donmai.us" } } @@ -95,10 +98,16 @@ class BooruManager: ObservableObject { if Task.isCancelled { return } DispatchQueue.main.async { - let newPosts = Array(Set(BooruPostXMLParser(data: data, provider: self.provider).parse())) - .sorted { lhs, rhs in - lhs.id > rhs.id - } + let newPosts = Array( + Set( + self.flavor == .danbooru + ? DanbooruPostParser(data: data).parse() + : BooruPostXMLParser(data: data, provider: self.provider).parse() + ) + ) + .sorted { lhs, rhs in + lhs.id > rhs.id + } if newPosts.isEmpty { self.endOfData = true @@ -166,6 +175,11 @@ class BooruManager: ObservableObject { let tagString = tags.joined(separator: "+") switch flavor { + case .danbooru: + return URL( + string: "https://\(domain)/posts.json?page=\(page)&tags=\(tagString)" + ) + case .moebooru: return URL(string: "https://\(domain)/post.xml?page=\(page)&limit=\(limit)&tags=\(tagString)") @@ -184,6 +198,9 @@ class BooruManager: ObservableObject { case .gelbooru: URL(string: "https://\(domain)/index.php?page=dapi&s=tag&q=index&limit=\(limit)") + + case .danbooru: + nil } } diff --git a/Sora/Data/Booru/BooruProvider.swift b/Sora/Data/Booru/BooruProvider.swift index 3331fd9..5458223 100644 --- a/Sora/Data/Booru/BooruProvider.swift +++ b/Sora/Data/Booru/BooruProvider.swift @@ -1,4 +1,5 @@ enum BooruProvider: String, CaseIterable, Decodable, Encodable { + case danbooru = "Danbooru" case gelbooru = "Gelbooru" case konachan = "Konachan.com" case safebooru = "Safebooru" diff --git a/Sora/Data/Booru/BooruProviderFlavor.swift b/Sora/Data/Booru/BooruProviderFlavor.swift index 69e3d1c..8917ab1 100644 --- a/Sora/Data/Booru/BooruProviderFlavor.swift +++ b/Sora/Data/Booru/BooruProviderFlavor.swift @@ -1,9 +1,13 @@ enum BooruProviderFlavor: String, CaseIterable, Decodable, Encodable { + case danbooru = "Danbooru" case gelbooru = "Gelbooru" case moebooru = "Moebooru" init(provider: BooruProvider) { switch provider { + case .danbooru: + self = .danbooru + case .yandere, .konachan, .sakugabooru: self = .moebooru diff --git a/Sora/Data/Booru/Post/BooruPost.swift b/Sora/Data/Booru/Post/BooruPost.swift index 09ba38b..458faa6 100644 --- a/Sora/Data/Booru/Post/BooruPost.swift +++ b/Sora/Data/Booru/Post/BooruPost.swift @@ -13,7 +13,7 @@ struct BooruPost: Identifiable, Hashable { let rating: BooruRating let tags: [String] let width: Int - let change: String + let change: String? let md5: String let creatorID: String let authorID: String? diff --git a/Sora/Data/Booru/Post/BooruPostXMLParser.swift b/Sora/Data/Booru/Post/BooruPostXMLParser.swift index fe14b7d..4960a79 100644 --- a/Sora/Data/Booru/Post/BooruPostXMLParser.swift +++ b/Sora/Data/Booru/Post/BooruPostXMLParser.swift @@ -59,7 +59,6 @@ class BooruPostXMLParser: NSObject, XMLParserDelegate { let change = attributeDict["change"], let md5 = attributeDict["md5"], let creatorId = attributeDict["creator_id"], - let hasChildrenStr = attributeDict["has_children"], let createdAt = attributeDict["created_at"], let status = attributeDict["status"], let source = attributeDict["source"], 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 + } +} diff --git a/Sora/Views/Post/Details/PostDetailsImageView.swift b/Sora/Views/Post/Details/PostDetailsImageView.swift index a46871c..702d0a0 100644 --- a/Sora/Views/Post/Details/PostDetailsImageView.swift +++ b/Sora/Views/Post/Details/PostDetailsImageView.swift @@ -141,6 +141,9 @@ struct PostDetailsImageView<Placeholder: View>: View { case .gelbooru: return URL(string: "https://\(manager.domain)/index.php?page=post&s=view&id=\(id)")! + + case .danbooru: + return URL(string: "https://\(manager.domain)/posts/\(id)")! } } |