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:
parent
489da8e82e
commit
78f2aff25b
7 changed files with 117 additions and 26 deletions
|
|
@ -36,6 +36,14 @@ class AllDebrid: PollingDebridSource, ObservableObject {
|
||||||
|
|
||||||
private let jsonDecoder = JSONDecoder()
|
private let jsonDecoder = JSONDecoder()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Populate user downloads and magnets
|
||||||
|
Task {
|
||||||
|
try? await getUserDownloads()
|
||||||
|
try? await getUserMagnets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Auth
|
// MARK: - Auth
|
||||||
|
|
||||||
// Fetches information for PIN auth
|
// Fetches information for PIN auth
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ class OffCloud: DebridSource, ObservableObject {
|
||||||
private let jsonDecoder = JSONDecoder()
|
private let jsonDecoder = JSONDecoder()
|
||||||
private let jsonEncoder = JSONEncoder()
|
private let jsonEncoder = JSONEncoder()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Populate user downloads and magnets
|
||||||
|
Task {
|
||||||
|
try? await getUserMagnets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setApiKey(_ key: String) {
|
func setApiKey(_ key: String) {
|
||||||
FerriteKeychain.shared.set(key, forKey: "OffCloud.ApiKey")
|
FerriteKeychain.shared.set(key, forKey: "OffCloud.ApiKey")
|
||||||
UserDefaults.standard.set(true, forKey: "OffCloud.UseManualKey")
|
UserDefaults.standard.set(true, forKey: "OffCloud.UseManualKey")
|
||||||
|
|
@ -133,11 +140,11 @@ class OffCloud: DebridSource, ObservableObject {
|
||||||
|
|
||||||
// Cloud in OffCloud's API
|
// Cloud in OffCloud's API
|
||||||
func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) {
|
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
|
// 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" }) {
|
if let existingCloudMagnet = cloudMagnets.first(where: { $0.hash == magnet.hash && $0.status == "downloaded" }) {
|
||||||
selectedMagnet = existingCloudMagnet
|
selectedCloudMagnet = existingCloudMagnet
|
||||||
} else {
|
} else {
|
||||||
let cloudDownloadResponse = try await offcloudDownload(magnet: magnet)
|
let cloudDownloadResponse = try await offcloudDownload(magnet: magnet)
|
||||||
|
|
||||||
|
|
@ -145,18 +152,19 @@ class OffCloud: DebridSource, ObservableObject {
|
||||||
throw DebridError.IsCaching
|
throw DebridError.IsCaching
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedMagnet = DebridCloudMagnet(
|
selectedCloudMagnet = DebridCloudMagnet(
|
||||||
id: cloudDownloadResponse.requestId,
|
id: cloudDownloadResponse.requestId,
|
||||||
fileName: cloudDownloadResponse.fileName,
|
fileName: cloudDownloadResponse.fileName,
|
||||||
status: cloudDownloadResponse.status,
|
status: cloudDownloadResponse.status,
|
||||||
hash: "",
|
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
|
var copiedIA = ia
|
||||||
|
|
||||||
copiedIA?.files = cloudExploreLinks.enumerated().compactMap { index, exploreLink in
|
copiedIA?.files = cloudExploreLinks.enumerated().compactMap { index, exploreLink in
|
||||||
|
|
@ -172,11 +180,17 @@ class OffCloud: DebridSource, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (nil, copiedIA)
|
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(
|
let restrictedFile = DebridIAFile(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: selectedMagnet.fileName,
|
name: selectedCloudMagnet.fileName,
|
||||||
streamUrlString: exploreLink
|
streamUrlString: "\(selectedCloudLink)/\(selectedCloudMagnet.fileName)"
|
||||||
)
|
)
|
||||||
|
|
||||||
return (restrictedFile, nil)
|
return (restrictedFile, nil)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,13 @@ class Premiumize: OAuthDebridSource, ObservableObject {
|
||||||
|
|
||||||
private let jsonDecoder = JSONDecoder()
|
private let jsonDecoder = JSONDecoder()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Populate user downloads and magnets
|
||||||
|
Task {
|
||||||
|
try? await getUserDownloads()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Auth
|
// MARK: - Auth
|
||||||
|
|
||||||
func getAuthUrl() throws -> URL {
|
func getAuthUrl() throws -> URL {
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,14 @@ class RealDebrid: PollingDebridSource, ObservableObject {
|
||||||
UserDefaults.standard.removeObject(forKey: forKey)
|
UserDefaults.standard.removeObject(forKey: forKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Populate user downloads and magnets
|
||||||
|
Task {
|
||||||
|
try? await getUserDownloads()
|
||||||
|
try? await getUserMagnets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Auth
|
// MARK: - Auth
|
||||||
|
|
||||||
// Fetches the device code from RD
|
// Fetches the device code from RD
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@ class TorBox: DebridSource, ObservableObject {
|
||||||
private let jsonDecoder = JSONDecoder()
|
private let jsonDecoder = JSONDecoder()
|
||||||
private let jsonEncoder = JSONEncoder()
|
private let jsonEncoder = JSONEncoder()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Populate user downloads and magnets
|
||||||
|
Task {
|
||||||
|
try? await getUserMagnets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Auth
|
// MARK: - Auth
|
||||||
|
|
||||||
func setApiKey(_ key: String) {
|
func setApiKey(_ key: String) {
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,14 @@ struct Magnet: Codable, Hashable, Sendable {
|
||||||
if let hash, link == nil {
|
if let hash, link == nil {
|
||||||
self.hash = parseHash(hash)
|
self.hash = parseHash(hash)
|
||||||
self.link = generateLink(hash: hash, title: title, trackers: trackers)
|
self.link = generateLink(hash: hash, title: title, trackers: trackers)
|
||||||
} else if let parsedLink = parseLink(link), hash == nil {
|
} else if let link, hash == nil {
|
||||||
self.link = parsedLink
|
let (link, hash) = parseLink(link)
|
||||||
self.hash = parseHash(extractHash(link: parsedLink))
|
|
||||||
|
self.link = link
|
||||||
|
self.hash = hash
|
||||||
} else {
|
} else {
|
||||||
self.hash = parseHash(hash)
|
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? {
|
func parseLink(_ link: String?, withHash: Bool = false) -> (link: String?, hash: String?) {
|
||||||
if let decodedLink = link?.removingPercentEncoding {
|
let separator = "magnet:?xt=urn:btih:"
|
||||||
let separator = "magnet:?xt=urn:btih:"
|
|
||||||
if decodedLink.starts(with: separator) {
|
// Remove percent encoding from the link and ensure it's a magnet
|
||||||
return decodedLink
|
guard let decodedLink = link?.removingPercentEncoding, decodedLink.contains(separator) else {
|
||||||
} else if decodedLink.contains(separator) {
|
return (nil, nil)
|
||||||
let splitLink = decodedLink.components(separatedBy: separator)
|
}
|
||||||
return splitLink.last.map { separator + $0 } ?? nil
|
|
||||||
} else {
|
// Isolate the magnet link if it's bundled with another protocol
|
||||||
return nil
|
let isolatedLink: String?
|
||||||
}
|
if decodedLink.starts(with: separator) {
|
||||||
|
isolatedLink = decodedLink
|
||||||
} else {
|
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension OffCloud {
|
extension OffCloud {
|
||||||
|
struct ErrorResponse: Codable, Sendable {
|
||||||
|
let error: String
|
||||||
|
}
|
||||||
|
|
||||||
struct InstantAvailabilityRequest: Codable, Sendable {
|
struct InstantAvailabilityRequest: Codable, Sendable {
|
||||||
let hashes: [String]
|
let hashes: [String]
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +32,32 @@ extension OffCloud {
|
||||||
let url: String
|
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 {
|
struct CloudHistoryResponse: Codable, Sendable {
|
||||||
let requestId: String
|
let requestId: String
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue