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 <bdashore3@proton.me>
This commit is contained in:
kingbri 2024-06-16 21:12:26 -05:00
parent 489da8e82e
commit 78f2aff25b
7 changed files with 117 additions and 26 deletions

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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

View file

@ -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) {

View file

@ -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)
}
}
}

View file

@ -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