diff options
Diffstat (limited to 'Sora/Data/Booru/BooruManager.swift')
| -rw-r--r-- | Sora/Data/Booru/BooruManager.swift | 105 |
1 files changed, 75 insertions, 30 deletions
diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift index 3c4374e..b70ad5b 100644 --- a/Sora/Data/Booru/BooruManager.swift +++ b/Sora/Data/Booru/BooruManager.swift @@ -26,10 +26,12 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng private let pageCache = NSCache<NSString, BooruPageCacheEntry>() // swiftlint:disable:this legacy_objc_type private let cacheDuration: TimeInterval private let credentials: BooruProviderCredentials? - private let userAgent: String + private let referer: String + private let userAgent: String? private let showHeldMoebooruPosts: Bool private var urlCache: [String: URL] = [:] private var lastPostCount = 0 + private var cachedMinimumPostID: Int? // MARK: - Computed Properties var tags: [String] { @@ -47,6 +49,8 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng _ provider: BooruProvider, credentials: BooruProviderCredentials? = nil, cacheDuration: TimeInterval = BooruPageCacheEntry.defaultExpiration, + sendUserAgent: Bool = true, + customUserAgent: String = "", showHeldMoebooruPosts: Bool = false ) { self.provider = provider @@ -58,10 +62,11 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng pageCache.countLimit = 50 pageCache.totalCostLimit = 50 * 1_024 * 1_024 - let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0" - let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1" - - self.userAgent = "Sora/\(version) (Build \(buildNumber))" + self.referer = BooruRequestConfiguration.baseReferer(for: provider.domain) + self.userAgent = BooruRequestConfiguration.resolvedUserAgent( + sendUserAgent: sendUserAgent, + customUserAgent: customUserAgent + ) let rootQuery = BooruSearchQuery( provider: provider, @@ -80,7 +85,7 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng let pageValue = flavor == .gelbooru ? page - 1 : page guard let url = url(forPosts: pageValue, limit: limit, tags: tags) else { return } - let cacheKey = "\(url.absoluteString.hashValue)_\(replace)" as NSString // swiftlint:disable:this legacy_objc_type + let cacheKey = pageCacheKey(for: url, replace: replace) if let cachedEntry = pageCache.object(forKey: cacheKey), !cachedEntry.isExpired @@ -303,7 +308,16 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng guard !Task.isCancelled else { return [] } - return BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count } + let parsedTags = + if flavor == .danbooru { + DanbooruTagParser(data: data).parse() + } else if flavor == .gelbooru, provider != .safebooru { + GelbooruAutocompleteTagParser(data: data).parse() + } else { + BooruTagXMLParser(data: data).parse() + } + + return parsedTags.sorted { $0.count > $1.count } } catch { if (error as? URLError)?.code != .cancelled { debugPrint("BooruManager.searchTags: \(error)") @@ -340,9 +354,7 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng guard page > 1 else { return "1" } guard !hasExplicitSortTag(in: tags) else { return String(page) } - guard let minimumPostID = posts.lazy.compactMap({ Int($0.id) }).min() else { - return String(page) - } + guard let minimumPostID = cachedMinimumPostID else { return String(page) } return "b\(minimumPostID)" } @@ -351,7 +363,11 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng let tagString = tags.joined(separator: "+") let pageCacheValue = flavor == .danbooru ? danbooruPageToken(for: page, tags: tags) : String(page) - let cacheKey = "posts_\(pageCacheValue)_\(limit)_\(tagString.hashValue)" + let cacheKey = postsURLCacheKey( + page: pageCacheValue, + limit: limit, + tagString: tagString + ) if let cachedURL = urlCache[cacheKey] { return cachedURL @@ -434,6 +450,16 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng return url } + // swiftlint:disable:next legacy_objc_type + private func pageCacheKey(for url: URL, replace: Bool) -> NSString { + // swiftlint:disable:next legacy_objc_type + "posts|\(url.absoluteString)|replace:\(replace)" as NSString + } + + private func postsURLCacheKey(page: String, limit: Int, tagString: String) -> String { + "posts|\(page)|limit:\(limit)|tags:\(tagString)" + } + private func url(forTags limit: Int, order: String = "count") -> URL? { switch flavor { case .moebooru: @@ -479,7 +505,7 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng components.host = domain components.path = "/tag.xml" components.queryItems = [ - URLQueryItem(name: "name_pattern", value: name), + URLQueryItem(name: "name", value: name), URLQueryItem(name: "order", value: "count"), ] @@ -492,28 +518,36 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng components.host = domain components.path = "/index.php" - var queryItems = [ - URLQueryItem(name: "page", value: "dapi"), - URLQueryItem(name: "s", value: "tag"), - URLQueryItem(name: "q", value: "index"), - URLQueryItem(name: "name_pattern", value: "%\(name)%"), - URLQueryItem(name: "orderby", value: "count"), - ] - - if let validCredentials = credentials, - !validCredentials.apiKey.isEmpty, - validCredentials.userID != 0 - { - queryItems.append(URLQueryItem(name: "api_key", value: validCredentials.apiKey)) - queryItems.append(URLQueryItem(name: "user_id", value: String(validCredentials.userID))) + if provider == .safebooru { + components.queryItems = [ + URLQueryItem(name: "page", value: "dapi"), + URLQueryItem(name: "s", value: "tag"), + URLQueryItem(name: "q", value: "index"), + URLQueryItem(name: "name_pattern", value: "%\(name)%"), + URLQueryItem(name: "orderby", value: "count"), + ] + } else { + components.queryItems = [ + URLQueryItem(name: "page", value: "autocomplete2"), + URLQueryItem(name: "type", value: "tag_query"), + URLQueryItem(name: "term", value: name), + ] } - components.queryItems = queryItems - return components.url case .danbooru: - return nil + var components = URLComponents() + + components.scheme = "https" + components.host = domain + components.path = "/tags.json" + components.queryItems = [ + URLQueryItem(name: "search[name_matches]", value: "\(name)*"), + URLQueryItem(name: "limit", value: "50"), + ] + + return components.url } } @@ -545,12 +579,17 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng postIndexMap.removeAll() lastPostCount = 0 + cachedMinimumPostID = nil } endOfData = newPosts.isEmpty guard !endOfData else { return } + if let nextMinimumPostID = newPosts.lazy.compactMap({ Int($0.id) }).min() { + cachedMinimumPostID = min(cachedMinimumPostID ?? nextMinimumPostID, nextMinimumPostID) + } + withTransaction(Transaction(animation: nil)) { let oldCount = self.posts.count @@ -567,7 +606,13 @@ class BooruManager: ObservableObject { // swiftlint:disable:this type_body_leng } func requestURL(_ url: URL) async throws -> Data { - try await AF.request(url, headers: ["User-Agent": userAgent]) + let request = BooruRequestConfiguration.request( + url: url, + referer: referer, + userAgent: userAgent + ) + + return try await AF.request(request) .validate(statusCode: 200..<300) .serializingData() .value |