From 78f2aff25b5a77504be1afa67db97137281a3776 Mon Sep 17 00:00:00 2001 From: kingbri Date: Sun, 16 Jun 2024 21:12:26 -0500 Subject: [PATCH] Debrid: Fix OffCloud single files and cloud population Populate cloud lists when the app is launched to begin maintainence of a synced list. In addition, fix the errors when OffCloud tried fetching links for a single file. The explore endpoint only works when the file is a batch which is unknown until it's actually called. Signed-off-by: kingbri --- Ferrite/API/AllDebridWrapper.swift | 8 ++++ Ferrite/API/OffCloudWrapper.swift | 32 ++++++++++----- Ferrite/API/PremiumizeWrapper.swift | 7 ++++ Ferrite/API/RealDebridWrapper.swift | 8 ++++ Ferrite/API/TorBoxWrapper.swift | 7 ++++ Ferrite/Models/DebridManagerModels.swift | 50 ++++++++++++++++-------- Ferrite/Models/OffCloudModels.swift | 31 ++++++++++++++- 7 files changed, 117 insertions(+), 26 deletions(-) diff --git a/Ferrite/API/AllDebridWrapper.swift b/Ferrite/API/AllDebridWrapper.swift index 93e23e4..0be8254 100644 --- a/Ferrite/API/AllDebridWrapper.swift +++ b/Ferrite/API/AllDebridWrapper.swift @@ -36,6 +36,14 @@ class AllDebrid: PollingDebridSource, ObservableObject { private let jsonDecoder = JSONDecoder() + init() { + // Populate user downloads and magnets + Task { + try? await getUserDownloads() + try? await getUserMagnets() + } + } + // MARK: - Auth // Fetches information for PIN auth diff --git a/Ferrite/API/OffCloudWrapper.swift b/Ferrite/API/OffCloudWrapper.swift index e2109a2..c1fabfc 100644 --- a/Ferrite/API/OffCloudWrapper.swift +++ b/Ferrite/API/OffCloudWrapper.swift @@ -37,6 +37,13 @@ class OffCloud: DebridSource, ObservableObject { private let jsonDecoder = JSONDecoder() private let jsonEncoder = JSONEncoder() + init() { + // Populate user downloads and magnets + Task { + try? await getUserMagnets() + } + } + func setApiKey(_ key: String) { FerriteKeychain.shared.set(key, forKey: "OffCloud.ApiKey") UserDefaults.standard.set(true, forKey: "OffCloud.UseManualKey") @@ -133,11 +140,11 @@ class OffCloud: DebridSource, ObservableObject { // Cloud in OffCloud's API func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) { - let selectedMagnet: DebridCloudMagnet + let selectedCloudMagnet: DebridCloudMagnet // Don't queue a new job if the magnet already exists in the user's account if let existingCloudMagnet = cloudMagnets.first(where: { $0.hash == magnet.hash && $0.status == "downloaded" }) { - selectedMagnet = existingCloudMagnet + selectedCloudMagnet = existingCloudMagnet } else { let cloudDownloadResponse = try await offcloudDownload(magnet: magnet) @@ -145,18 +152,19 @@ class OffCloud: DebridSource, ObservableObject { throw DebridError.IsCaching } - selectedMagnet = DebridCloudMagnet( + selectedCloudMagnet = DebridCloudMagnet( id: cloudDownloadResponse.requestId, fileName: cloudDownloadResponse.fileName, status: cloudDownloadResponse.status, hash: "", - links: [] + links: [cloudDownloadResponse.url] ) } - let cloudExploreLinks = try await cloudExplore(requestId: selectedMagnet.id) + let cloudExploreResponse = try await cloudExplore(requestId: selectedCloudMagnet.id) - if cloudExploreLinks.count > 1 { + // Request will error if the file isn't a batch + if case let .links(cloudExploreLinks) = cloudExploreResponse { var copiedIA = ia copiedIA?.files = cloudExploreLinks.enumerated().compactMap { index, exploreLink in @@ -172,11 +180,17 @@ class OffCloud: DebridSource, ObservableObject { } return (nil, copiedIA) - } else if let exploreLink = cloudExploreLinks.first { + } else if case let .error(cloudExploreError) = cloudExploreResponse, + cloudExploreError.error.lowercased() == "bad archive" + { + guard let selectedCloudLink = selectedCloudMagnet.links[safe: 0] else { + throw DebridError.EmptyUserMagnets + } + let restrictedFile = DebridIAFile( id: 0, - name: selectedMagnet.fileName, - streamUrlString: exploreLink + name: selectedCloudMagnet.fileName, + streamUrlString: "\(selectedCloudLink)/\(selectedCloudMagnet.fileName)" ) return (restrictedFile, nil) diff --git a/Ferrite/API/PremiumizeWrapper.swift b/Ferrite/API/PremiumizeWrapper.swift index d1d5255..c83ee0c 100644 --- a/Ferrite/API/PremiumizeWrapper.swift +++ b/Ferrite/API/PremiumizeWrapper.swift @@ -38,6 +38,13 @@ class Premiumize: OAuthDebridSource, ObservableObject { private let jsonDecoder = JSONDecoder() + init() { + // Populate user downloads and magnets + Task { + try? await getUserDownloads() + } + } + // MARK: - Auth func getAuthUrl() throws -> URL { diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 773ff87..c64cbf1 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -49,6 +49,14 @@ class RealDebrid: PollingDebridSource, ObservableObject { UserDefaults.standard.removeObject(forKey: forKey) } + init() { + // Populate user downloads and magnets + Task { + try? await getUserDownloads() + try? await getUserMagnets() + } + } + // MARK: - Auth // Fetches the device code from RD diff --git a/Ferrite/API/TorBoxWrapper.swift b/Ferrite/API/TorBoxWrapper.swift index e4f34bd..c51e6ba 100644 --- a/Ferrite/API/TorBoxWrapper.swift +++ b/Ferrite/API/TorBoxWrapper.swift @@ -36,6 +36,13 @@ class TorBox: DebridSource, ObservableObject { private let jsonDecoder = JSONDecoder() private let jsonEncoder = JSONEncoder() + init() { + // Populate user downloads and magnets + Task { + try? await getUserMagnets() + } + } + // MARK: - Auth func setApiKey(_ key: String) { diff --git a/Ferrite/Models/DebridManagerModels.swift b/Ferrite/Models/DebridManagerModels.swift index ad4cff6..56ea691 100644 --- a/Ferrite/Models/DebridManagerModels.swift +++ b/Ferrite/Models/DebridManagerModels.swift @@ -55,12 +55,14 @@ struct Magnet: Codable, Hashable, Sendable { if let hash, link == nil { self.hash = parseHash(hash) self.link = generateLink(hash: hash, title: title, trackers: trackers) - } else if let parsedLink = parseLink(link), hash == nil { - self.link = parsedLink - self.hash = parseHash(extractHash(link: parsedLink)) + } else if let link, hash == nil { + let (link, hash) = parseLink(link) + + self.link = link + self.hash = hash } else { self.hash = parseHash(hash) - self.link = parseLink(link) + self.link = parseLink(link).link } } @@ -108,19 +110,35 @@ struct Magnet: Codable, Hashable, Sendable { } } - func parseLink(_ link: String?) -> String? { - if let decodedLink = link?.removingPercentEncoding { - let separator = "magnet:?xt=urn:btih:" - if decodedLink.starts(with: separator) { - return decodedLink - } else if decodedLink.contains(separator) { - let splitLink = decodedLink.components(separatedBy: separator) - return splitLink.last.map { separator + $0 } ?? nil - } else { - return nil - } + func parseLink(_ link: String?, withHash: Bool = false) -> (link: String?, hash: String?) { + let separator = "magnet:?xt=urn:btih:" + + // Remove percent encoding from the link and ensure it's a magnet + guard let decodedLink = link?.removingPercentEncoding, decodedLink.contains(separator) else { + return (nil, nil) + } + + // Isolate the magnet link if it's bundled with another protocol + let isolatedLink: String? + if decodedLink.starts(with: separator) { + isolatedLink = decodedLink } else { - return nil + let splitLink = decodedLink.components(separatedBy: separator) + isolatedLink = splitLink.last.map { separator + $0 } + } + + guard let isolatedLink else { + return (nil, nil) + } + + // If the hash can be extracted, decrypt it if necessary and return the revised link + hash + if let originalHash = extractHash(link: isolatedLink), + let parsedHash = parseHash(originalHash) + { + let replacedLink = isolatedLink.replacingOccurrences(of: originalHash, with: parsedHash) + return (replacedLink, parsedHash) + } else { + return (decodedLink, nil) } } } diff --git a/Ferrite/Models/OffCloudModels.swift b/Ferrite/Models/OffCloudModels.swift index 0485563..3781fe9 100644 --- a/Ferrite/Models/OffCloudModels.swift +++ b/Ferrite/Models/OffCloudModels.swift @@ -8,6 +8,10 @@ import Foundation extension OffCloud { + struct ErrorResponse: Codable, Sendable { + let error: String + } + struct InstantAvailabilityRequest: Codable, Sendable { let hashes: [String] } @@ -28,7 +32,32 @@ extension OffCloud { let url: String } - typealias CloudExploreResponse = [String] + enum CloudExploreResponse: Codable { + case links([String]) + case error(ErrorResponse) + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + // Only continue if the data is a List which indicates a success + if let linkArray = try? container.decode([String].self) { + self = .links(linkArray) + } else { + let value = try container.decode(ErrorResponse.self) + self = .error(value) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .links(array): + try container.encode(array) + case let .error(value): + try container.encode(value) + } + } + } struct CloudHistoryResponse: Codable, Sendable { let requestId: String