summaryrefslogtreecommitdiff
path: root/Sora/Data/Booru
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-02-22 00:07:44 -0800
committerFuwn <[email protected]>2025-02-22 00:07:44 -0800
commite42fa12dafe264c665d2574c93b54ddafe7f2e1f (patch)
treea362b3b78ea97dc28ce5cc3682801bf89688f546 /Sora/Data/Booru
parentfeat: Development commit (diff)
downloadsora-testing-e42fa12dafe264c665d2574c93b54ddafe7f2e1f.tar.xz
sora-testing-e42fa12dafe264c665d2574c93b54ddafe7f2e1f.zip
feat: Development commit
Diffstat (limited to 'Sora/Data/Booru')
-rw-r--r--Sora/Data/Booru/BooruManager.swift224
-rw-r--r--Sora/Data/Booru/BooruPost.swift46
-rw-r--r--Sora/Data/Booru/BooruPostFileType.swift6
-rw-r--r--Sora/Data/Booru/BooruPostXMLParser.swift208
-rw-r--r--Sora/Data/Booru/BooruProvider.swift18
-rw-r--r--Sora/Data/Booru/BooruTag.swift10
-rw-r--r--Sora/Data/Booru/BooruTagXMLParser.swift92
7 files changed, 312 insertions, 292 deletions
diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift
index dd63367..f86e3a9 100644
--- a/Sora/Data/Booru/BooruManager.swift
+++ b/Sora/Data/Booru/BooruManager.swift
@@ -2,143 +2,151 @@ import SwiftUI
@MainActor
class BooruManager: ObservableObject {
- @Published var posts: [BooruPost] = []
- @Published var allTags: [BooruTag] = []
- @Published var isLoading: Bool = false
- @Published var currentPage: Int = 1
- @Published var searchText = ""
- @Published var endOfData: Bool = false
- #if os(macOS)
- @Published var selectedPost: BooruPost?
- #endif
- private var currentTask: Task<Void, Never>?
- let provider: BooruProvider?
- var tags: [String] {
- if searchText.isEmpty {
- return []
- }
-
- return searchText
- .split(separator: " ")
- .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
- .filter { !$0.isEmpty }
+ @Published var posts: [BooruPost] = []
+ @Published var allTags: [BooruTag] = []
+ @Published var isLoading: Bool = false
+ @Published var currentPage: Int = 1
+ @Published var searchText = ""
+ @Published var endOfData: Bool = false
+ #if os(macOS)
+ @Published var selectedPost: BooruPost?
+ #endif
+ private var currentTask: Task<Void, Never>?
+ let provider: BooruProvider?
+ var tags: [String] {
+ if searchText.isEmpty {
+ return []
}
- init(_ provider: BooruProvider? = nil) {
- self.provider = provider
+ return
+ searchText
+ .split(separator: " ")
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
+ .filter { !$0.isEmpty }
+ }
- fetchAllTags()
- }
+ init(_ provider: BooruProvider? = nil) {
+ self.provider = provider
+
+ fetchAllTags()
+ }
- func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async {
- guard !isLoading else { return }
- guard provider != nil else { return }
+ func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async
+ {
+ guard !isLoading else { return }
+ guard provider != nil else { return }
- currentTask?.cancel()
+ currentTask?.cancel()
- currentTask = Task {
- isLoading = true
+ currentTask = Task {
+ isLoading = true
- defer { isLoading = false }
+ defer { isLoading = false }
- if replace {
- self.posts = []
- self.currentPage = 1
- }
+ if replace {
+ self.posts = []
+ self.currentPage = 1
+ }
- guard let url = urlForPosts(page: self.provider == .safebooru ? page - 1 : page, limit: limit, tags: tags) else {
- return
- }
+ guard
+ let url = urlForPosts(
+ page: self.provider == .safebooru ? page - 1 : page, limit: limit, tags: tags)
+ else {
+ return
+ }
- do {
- let (data, _) = try await URLSession.shared.data(from: url)
+ do {
+ let (data, _) = try await URLSession.shared.data(from: url)
- if Task.isCancelled { return }
+ if Task.isCancelled { return }
- DispatchQueue.main.async {
- let newPosts = Array(Set(BooruPostXMLParser(data: data).parse())).sorted { $0.id > $1.id }
+ DispatchQueue.main.async {
+ let newPosts = Array(Set(BooruPostXMLParser(data: data).parse())).sorted { $0.id > $1.id }
- if newPosts.isEmpty {
- self.endOfData = true
- } else {
- self.posts += Array(Set(newPosts))
- }
- }
- } catch {
- if (error as? URLError)?.code != .cancelled {
- #if DEBUG
- print("fetchPosts: \(error)")
- #endif
- }
- }
+ if newPosts.isEmpty {
+ self.endOfData = true
+ } else {
+ self.posts += Array(Set(newPosts))
+ }
}
+ } catch {
+ if (error as? URLError)?.code != .cancelled {
+ #if DEBUG
+ print("fetchPosts: \(error)")
+ #endif
+ }
+ }
}
+ }
- func performSearch() {
- currentTask?.cancel()
+ func performSearch() {
+ currentTask?.cancel()
- currentTask = Task {
- await fetchPosts(page: 1, tags: tags, replace: true)
- }
+ currentTask = Task {
+ await fetchPosts(page: 1, tags: tags, replace: true)
}
+ }
- func loadNextPage() {
- guard !isLoading else { return }
+ func loadNextPage() {
+ guard !isLoading else { return }
- Task {
- await fetchPosts(page: currentPage + 1, tags: tags)
+ Task {
+ await fetchPosts(page: currentPage + 1, tags: tags)
- DispatchQueue.main.async {
- self.currentPage += 1
- }
- }
+ DispatchQueue.main.async {
+ self.currentPage += 1
+ }
}
+ }
- func fetchAllTags(limit: Int = 100_000) {
- guard provider != nil else { return }
+ func fetchAllTags(limit: Int = 100_000) {
+ guard provider != nil else { return }
- Task {
- guard let url = urlForTags(limit: limit) else { return }
+ Task {
+ guard let url = urlForTags(limit: limit) else { return }
- do {
- let (data, _) = try await URLSession.shared.data(from: url)
+ do {
+ let (data, _) = try await URLSession.shared.data(from: url)
- if Task.isCancelled { return }
+ if Task.isCancelled { return }
- DispatchQueue.main.async {
- self.allTags = BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count }
- }
- } catch {
- if (error as? URLError)?.code != .cancelled {
- #if DEBUG
- print("fetchAllTags: \(error)")
- #endif
- }
- }
+ DispatchQueue.main.async {
+ self.allTags = BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count }
}
- }
-
- private func urlForPosts(page: Int, limit: Int, tags: [String]) -> URL? {
- let tagString = tags.joined(separator: "+")
-
- switch provider {
- case .yandere:
- return URL(string: "https://yande.re/post.xml?page=\(page)&limit=\(limit)&tags=\(tagString)")
- case .safebooru:
- return URL(string: "https://safebooru.org/index.php?page=dapi&s=post&q=index&pid=\(page)&limit=\(limit)&tags=\(tagString)")
- default:
- return nil
+ } catch {
+ if (error as? URLError)?.code != .cancelled {
+ #if DEBUG
+ print("fetchAllTags: \(error)")
+ #endif
}
+ }
}
-
- private func urlForTags(limit: Int) -> URL? {
- switch provider {
- case .yandere:
- URL(string: "https://yande.re/tag.xml?limit=\(limit)")
- case .safebooru:
- URL(string: "https://safebooru.org/index.php?page=dapi&s=tag&q=index&limit=\(limit)")
- default:
- nil
- }
+ }
+
+ private func urlForPosts(page: Int, limit: Int, tags: [String]) -> URL? {
+ let tagString = tags.joined(separator: "+")
+
+ switch provider {
+ case .yandere:
+ return URL(string: "https://yande.re/post.xml?page=\(page)&limit=\(limit)&tags=\(tagString)")
+ case .safebooru:
+ return URL(
+ string:
+ "https://safebooru.org/index.php?page=dapi&s=post&q=index&pid=\(page)&limit=\(limit)&tags=\(tagString)"
+ )
+ default:
+ return nil
+ }
+ }
+
+ private func urlForTags(limit: Int) -> URL? {
+ switch provider {
+ case .yandere:
+ URL(string: "https://yande.re/tag.xml?limit=\(limit)")
+ case .safebooru:
+ URL(string: "https://safebooru.org/index.php?page=dapi&s=tag&q=index&limit=\(limit)")
+ default:
+ nil
}
+ }
}
diff --git a/Sora/Data/Booru/BooruPost.swift b/Sora/Data/Booru/BooruPost.swift
index 59b8952..7f995a3 100644
--- a/Sora/Data/Booru/BooruPost.swift
+++ b/Sora/Data/Booru/BooruPost.swift
@@ -1,27 +1,27 @@
import Foundation
struct BooruPost: Identifiable, Hashable {
- let id: String
- let height: Int
- let score: String
- let fileURL: URL
- let parentID: String
- let sampleURL: URL
- let sampleWidth: Int
- let sampleHeight: Int
- let previewURL: URL
- let rating: String
- let tags: [String]
- let width: Int
- let change: String
- let md5: String
- let creatorID: String
- let hasChildren: Bool
- let createdAt: Date
- let status: String
- let source: String
- let hasNotes: Bool
- let hasComments: Bool
- let previewWidth: Int
- let previewHeight: Int
+ let id: String
+ let height: Int
+ let score: String
+ let fileURL: URL
+ let parentID: String
+ let sampleURL: URL
+ let sampleWidth: Int
+ let sampleHeight: Int
+ let previewURL: URL
+ let rating: String
+ let tags: [String]
+ let width: Int
+ let change: String
+ let md5: String
+ let creatorID: String
+ let hasChildren: Bool
+ let createdAt: Date
+ let status: String
+ let source: String
+ let hasNotes: Bool
+ let hasComments: Bool
+ let previewWidth: Int
+ let previewHeight: Int
}
diff --git a/Sora/Data/Booru/BooruPostFileType.swift b/Sora/Data/Booru/BooruPostFileType.swift
index 71d0352..62900db 100644
--- a/Sora/Data/Booru/BooruPostFileType.swift
+++ b/Sora/Data/Booru/BooruPostFileType.swift
@@ -1,5 +1,5 @@
enum BooruPostFileType: String, CaseIterable {
- case original
- case sample
- case preview
+ case original
+ case sample
+ case preview
}
diff --git a/Sora/Data/Booru/BooruPostXMLParser.swift b/Sora/Data/Booru/BooruPostXMLParser.swift
index 30207bc..79827aa 100644
--- a/Sora/Data/Booru/BooruPostXMLParser.swift
+++ b/Sora/Data/Booru/BooruPostXMLParser.swift
@@ -1,116 +1,122 @@
import Foundation
class BooruPostXMLParser: NSObject, XMLParserDelegate {
- private var posts: [BooruPost] = []
- private var currentPost: BooruPost?
- private var parser: XMLParser
-
- init(data: Data) {
- parser = XMLParser(data: data)
-
- super.init()
-
- parser.delegate = self
+ private var posts: [BooruPost] = []
+ private var currentPost: BooruPost?
+ private var parser: XMLParser
+
+ init(data: Data) {
+ parser = XMLParser(data: data)
+
+ super.init()
+
+ parser.delegate = self
+ }
+
+ func parse() -> [BooruPost] {
+ parser.parse()
+
+ return posts
+ }
+
+ func parser(
+ _: XMLParser, didStartElement elementName: String, namespaceURI _: String?,
+ qualifiedName _: String?, attributes attributeDict: [String: String] = [:]
+ ) {
+ if elementName == "post" {
+ guard let id = attributeDict["id"],
+ let heightStr = attributeDict["height"],
+ let height = Int(heightStr),
+ let score = attributeDict["score"],
+ let fileUrl = attributeDict["file_url"],
+ let parentId = attributeDict["parent_id"],
+ let sampleUrl = attributeDict["sample_url"],
+ let sampleWidthStr = attributeDict["sample_width"],
+ let sampleWidth = Int(sampleWidthStr),
+ let sampleHeightStr = attributeDict["sample_height"],
+ let sampleHeight = Int(sampleHeightStr),
+ let previewUrl = attributeDict["preview_url"],
+ let rating = attributeDict["rating"],
+ let tags = attributeDict["tags"],
+ let widthStr = attributeDict["width"],
+ let width = Int(widthStr),
+ 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"],
+ let previewWidthStr = attributeDict["preview_width"],
+ let previewWidth = Int(previewWidthStr),
+ let previewHeightStr = attributeDict["preview_height"],
+ let previewHeight = Int(previewHeightStr)
+ else {
+ return
+ }
+
+ let hasNotesStr = attributeDict["has_notes"] ?? "false"
+ let hasCommentsStr = attributeDict["has_comments"] ?? "false"
+
+ currentPost = BooruPost(
+ id: id,
+ height: height,
+ score: score,
+ fileURL: URL(string: fileUrl)!,
+ parentID: parentId,
+ sampleURL: URL(string: sampleUrl)!,
+ sampleWidth: sampleWidth,
+ sampleHeight: sampleHeight,
+ previewURL: URL(string: previewUrl)!,
+ rating: rating,
+ tags: tags.components(separatedBy: " ").filter { !$0.isEmpty },
+ width: width,
+ change: change,
+ md5: md5,
+ creatorID: creatorId,
+ hasChildren: hasChildrenStr == "true",
+ createdAt: parseCreatedAt(createdAt)!,
+ status: status,
+ source: source,
+ hasNotes: hasNotesStr == "true",
+ hasComments: hasCommentsStr == "true",
+ previewWidth: previewWidth,
+ previewHeight: previewHeight
+ )
}
+ }
- func parse() -> [BooruPost] {
- parser.parse()
-
- return posts
- }
+ func parser(
+ _: XMLParser, didEndElement elementName: String, namespaceURI _: String?,
+ qualifiedName _: String?
+ ) {
+ if elementName == "post", let post = currentPost {
+ posts.append(post)
- func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) {
- if elementName == "post" {
- guard let id = attributeDict["id"],
- let heightStr = attributeDict["height"],
- let height = Int(heightStr),
- let score = attributeDict["score"],
- let fileUrl = attributeDict["file_url"],
- let parentId = attributeDict["parent_id"],
- let sampleUrl = attributeDict["sample_url"],
- let sampleWidthStr = attributeDict["sample_width"],
- let sampleWidth = Int(sampleWidthStr),
- let sampleHeightStr = attributeDict["sample_height"],
- let sampleHeight = Int(sampleHeightStr),
- let previewUrl = attributeDict["preview_url"],
- let rating = attributeDict["rating"],
- let tags = attributeDict["tags"],
- let widthStr = attributeDict["width"],
- let width = Int(widthStr),
- 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"],
- let previewWidthStr = attributeDict["preview_width"],
- let previewWidth = Int(previewWidthStr),
- let previewHeightStr = attributeDict["preview_height"],
- let previewHeight = Int(previewHeightStr)
- else {
- return
- }
-
- let hasNotesStr = attributeDict["has_notes"] ?? "false"
- let hasCommentsStr = attributeDict["has_comments"] ?? "false"
-
- currentPost = BooruPost(
- id: id,
- height: height,
- score: score,
- fileURL: URL(string: fileUrl)!,
- parentID: parentId,
- sampleURL: URL(string: sampleUrl)!,
- sampleWidth: sampleWidth,
- sampleHeight: sampleHeight,
- previewURL: URL(string: previewUrl)!,
- rating: rating,
- tags: tags.components(separatedBy: " ").filter { !$0.isEmpty },
- width: width,
- change: change,
- md5: md5,
- creatorID: creatorId,
- hasChildren: hasChildrenStr == "true",
- createdAt: parseCreatedAt(createdAt)!,
- status: status,
- source: source,
- hasNotes: hasNotesStr == "true",
- hasComments: hasCommentsStr == "true",
- previewWidth: previewWidth,
- previewHeight: previewHeight
- )
- }
+ currentPost = nil
}
+ }
- func parser(_: XMLParser, didEndElement elementName: String, namespaceURI _: String?, qualifiedName _: String?) {
- if elementName == "post", let post = currentPost {
- posts.append(post)
-
- currentPost = nil
- }
+ #if DEBUG
+ func parser(_: XMLParser, parseErrorOccurred parseError: any Error) {
+ print("parser: \(parseError)")
}
+ #endif
- #if DEBUG
- func parser(_: XMLParser, parseErrorOccurred parseError: any Error) {
- print("parser: \(parseError)")
- }
- #endif
-
- func parseCreatedAt(_ input: String) -> Date? {
- let dateFormatter = DateFormatter()
-
- dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
- dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+ func parseCreatedAt(_ input: String) -> Date? {
+ let dateFormatter = DateFormatter()
- if let date = dateFormatter.date(from: input) {
- return date
- }
+ dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
- if let timestamp = Double(input) {
- return Date(timeIntervalSince1970: timestamp)
- }
+ if let date = dateFormatter.date(from: input) {
+ return date
+ }
- return nil
+ if let timestamp = Double(input) {
+ return Date(timeIntervalSince1970: timestamp)
}
+
+ return nil
+ }
}
diff --git a/Sora/Data/Booru/BooruProvider.swift b/Sora/Data/Booru/BooruProvider.swift
index 73bb4f2..994e4e3 100644
--- a/Sora/Data/Booru/BooruProvider.swift
+++ b/Sora/Data/Booru/BooruProvider.swift
@@ -1,13 +1,13 @@
enum BooruProvider: String, CaseIterable, Decodable, Encodable {
- case yandere
- case safebooru
+ case yandere
+ case safebooru
- func formatted() -> String {
- switch self {
- case .yandere:
- "yande.re"
- case .safebooru:
- "Safebooru"
- }
+ func formatted() -> String {
+ switch self {
+ case .yandere:
+ "yande.re"
+ case .safebooru:
+ "Safebooru"
}
+ }
}
diff --git a/Sora/Data/Booru/BooruTag.swift b/Sora/Data/Booru/BooruTag.swift
index b98bc75..d603e9f 100644
--- a/Sora/Data/Booru/BooruTag.swift
+++ b/Sora/Data/Booru/BooruTag.swift
@@ -1,9 +1,9 @@
import Foundation
struct BooruTag: Identifiable, Hashable {
- let id: String
- let name: String
- let count: Int
- let type: Int
- let ambiguous: Bool
+ let id: String
+ let name: String
+ let count: Int
+ let type: Int
+ let ambiguous: Bool
}
diff --git a/Sora/Data/Booru/BooruTagXMLParser.swift b/Sora/Data/Booru/BooruTagXMLParser.swift
index 0dfebc3..bba26c5 100644
--- a/Sora/Data/Booru/BooruTagXMLParser.swift
+++ b/Sora/Data/Booru/BooruTagXMLParser.swift
@@ -1,58 +1,64 @@
import Foundation
class BooruTagXMLParser: NSObject, XMLParserDelegate {
- private var tags: [BooruTag] = []
- private var currentTag: BooruTag?
- private var parser: XMLParser
+ private var tags: [BooruTag] = []
+ private var currentTag: BooruTag?
+ private var parser: XMLParser
- init(data: Data) {
- parser = XMLParser(data: data)
+ init(data: Data) {
+ parser = XMLParser(data: data)
- super.init()
+ super.init()
- parser.delegate = self
- }
+ parser.delegate = self
+ }
- func parse() -> [BooruTag] {
- parser.parse()
+ func parse() -> [BooruTag] {
+ parser.parse()
- return tags
- }
+ return tags
+ }
- func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) {
- if elementName == "tag" {
- guard let id = attributeDict["id"],
- let name = attributeDict["name"],
- let countStr = attributeDict["count"],
- let count = Int(countStr),
- let typeStr = attributeDict["type"],
- let type = Int(typeStr),
- let ambiguousStr = attributeDict["ambiguous"]
- else {
- return
- }
-
- currentTag = BooruTag(
- id: id,
- name: name,
- count: count,
- type: type,
- ambiguous: ambiguousStr == "true"
- )
- }
+ func parser(
+ _: XMLParser, didStartElement elementName: String, namespaceURI _: String?,
+ qualifiedName _: String?, attributes attributeDict: [String: String] = [:]
+ ) {
+ if elementName == "tag" {
+ guard let id = attributeDict["id"],
+ let name = attributeDict["name"],
+ let countStr = attributeDict["count"],
+ let count = Int(countStr),
+ let typeStr = attributeDict["type"],
+ let type = Int(typeStr),
+ let ambiguousStr = attributeDict["ambiguous"]
+ else {
+ return
+ }
+
+ currentTag = BooruTag(
+ id: id,
+ name: name,
+ count: count,
+ type: type,
+ ambiguous: ambiguousStr == "true"
+ )
}
+ }
- func parser(_: XMLParser, didEndElement elementName: String, namespaceURI _: String?, qualifiedName _: String?) {
- if elementName == "tag", let tag = currentTag {
- tags.append(tag)
+ func parser(
+ _: XMLParser, didEndElement elementName: String, namespaceURI _: String?,
+ qualifiedName _: String?
+ ) {
+ if elementName == "tag", let tag = currentTag {
+ tags.append(tag)
- currentTag = nil
- }
+ currentTag = nil
}
+ }
- #if DEBUG
- func parser(_: XMLParser, parseErrorOccurred parseError: any Error) {
- print(parseError)
- }
- #endif
+ #if DEBUG
+ func parser(_: XMLParser, parseErrorOccurred parseError: any Error) {
+ print(parseError)
+ }
+ #endif
}