Debrid: Add InstantAvailability and download to protocol

Unify IA into a passable client side structure and add a common
download method to the DebridSource protocol.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2024-06-02 23:08:05 -04:00 committed by Brian Dashore
parent b8a225e141
commit 69a9d30475
11 changed files with 160 additions and 142 deletions

View file

@ -394,6 +394,7 @@
0C6C7C9C29315292002DF910 /* AllDebridModels.swift */, 0C6C7C9C29315292002DF910 /* AllDebridModels.swift */,
0C7ED14028D61BBA009E29AD /* BackupModels.swift */, 0C7ED14028D61BBA009E29AD /* BackupModels.swift */,
0C0755C7293425B500ECA142 /* DebridManagerModels.swift */, 0C0755C7293425B500ECA142 /* DebridManagerModels.swift */,
0CF1ABE12C0C3D2F009F6C26 /* DebridModels.swift */,
0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */, 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */,
0C68135128BC1A7C00FAD890 /* GithubModels.swift */, 0C68135128BC1A7C00FAD890 /* GithubModels.swift */,
0C422E7F293542F300486D65 /* PremiumizeModels.swift */, 0C422E7F293542F300486D65 /* PremiumizeModels.swift */,
@ -403,7 +404,6 @@
0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */, 0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */,
0C6771F929B3D1AE005D38D2 /* KodiModels.swift */, 0C6771F929B3D1AE005D38D2 /* KodiModels.swift */,
0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */, 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */,
0CF1ABE12C0C3D2F009F6C26 /* DebridModels.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";

View file

@ -9,7 +9,6 @@ import Foundation
// TODO: Fix errors // TODO: Fix errors
public class AllDebrid: PollingDebridSource { public class AllDebrid: PollingDebridSource {
public let id = "AllDebrid" public let id = "AllDebrid"
public var authTask: Task<Void, Error>? public var authTask: Task<Void, Error>?
@ -96,7 +95,7 @@ public class AllDebrid: PollingDebridSource {
} }
public func getToken() -> String? { public func getToken() -> String? {
return FerriteKeychain.shared.get("AllDebrid.ApiKey") FerriteKeychain.shared.get("AllDebrid.ApiKey")
} }
// Clears tokens. No endpoint to deregister a device // Clears tokens. No endpoint to deregister a device
@ -146,6 +145,20 @@ public class AllDebrid: PollingDebridSource {
} }
} }
// Wrapper function to fetch a download link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
let magnetID = try await addMagnet(magnet: magnet)
let lockedLink = try await fetchMagnetStatus(
magnetId: magnetID,
selectedIndex: iaFile?.fileId ?? 0
)
try await saveLink(link: lockedLink)
let downloadUrl = try await unlockLink(lockedLink: lockedLink)
return downloadUrl
}
// Adds a magnet link to the user's AD account // Adds a magnet link to the user's AD account
public func addMagnet(magnet: Magnet) async throws -> Int { public func addMagnet(magnet: Magnet) async throws -> Int {
guard let magnetLink = magnet.link else { guard let magnetLink = magnet.link else {
@ -255,7 +268,7 @@ public class AllDebrid: PollingDebridSource {
try await performRequest(request: &request, requestName: #function) try await performRequest(request: &request, requestName: #function)
} }
public func instantAvailability(magnets: [Magnet]) async throws -> [IA] { public func instantAvailability(magnets: [Magnet]) async throws -> [DebridIA] {
let queryItems = magnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) } let queryItems = magnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) }
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/instant", queryItems: queryItems)) var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/instant", queryItems: queryItems))
@ -266,10 +279,10 @@ public class AllDebrid: PollingDebridSource {
let availableHashes = filteredMagnets.map { magnetResp in let availableHashes = filteredMagnets.map { magnetResp in
// Force unwrap is OK here since the filter caught any nil values // Force unwrap is OK here since the filter caught any nil values
let files = magnetResp.files!.enumerated().map { index, magnetFile in let files = magnetResp.files!.enumerated().map { index, magnetFile in
IAFile(id: index, fileName: magnetFile.name) DebridIAFile(fileId: index, name: magnetFile.name)
} }
return IA( return DebridIA(
magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet), magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet),
expiryTimeStamp: Date().timeIntervalSince1970 + 300, expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files files: files

View file

@ -8,7 +8,6 @@
import Foundation import Foundation
public class Premiumize: OAuthDebridSource { public class Premiumize: OAuthDebridSource {
public let id = "Premiumize" public let id = "Premiumize"
let baseAuthUrl = "https://www.premiumize.me/authorize" let baseAuthUrl = "https://www.premiumize.me/authorize"
@ -58,7 +57,7 @@ public class Premiumize: OAuthDebridSource {
} }
public func getToken() -> String? { public func getToken() -> String? {
return FerriteKeychain.shared.get("Premiumize.AccessToken") FerriteKeychain.shared.get("Premiumize.AccessToken")
} }
// Clears tokens. No endpoint to deregister a device // Clears tokens. No endpoint to deregister a device
@ -162,15 +161,15 @@ public class Premiumize: OAuthDebridSource {
// Function to divide and execute DDL endpoint requests in parallel // Function to divide and execute DDL endpoint requests in parallel
// Calls this for 10 requests at a time to not overwhelm API servers // Calls this for 10 requests at a time to not overwhelm API servers
public func divideDDLRequests(magnetChunk: [Magnet]) async throws -> [IA] { public func divideDDLRequests(magnetChunk: [Magnet]) async throws -> [DebridIA] {
let tempIA = try await withThrowingTaskGroup(of: Premiumize.IA.self) { group in let tempIA = try await withThrowingTaskGroup(of: DebridIA.self) { group in
for magnet in magnetChunk { for magnet in magnetChunk {
group.addTask { group.addTask {
try await self.fetchDDL(magnet: magnet) try await self.fetchDDL(magnet: magnet)
} }
} }
var chunkedIA: [Premiumize.IA] = [] var chunkedIA: [DebridIA] = []
for try await ia in group { for try await ia in group {
chunkedIA.append(ia) chunkedIA.append(ia)
} }
@ -181,7 +180,7 @@ public class Premiumize: OAuthDebridSource {
} }
// Grabs DDL links // Grabs DDL links
func fetchDDL(magnet: Magnet) async throws -> IA { func fetchDDL(magnet: Magnet) async throws -> DebridIA {
if magnet.hash == nil { if magnet.hash == nil {
throw PMError.EmptyData throw PMError.EmptyData
} }
@ -200,13 +199,14 @@ public class Premiumize: OAuthDebridSource {
if !rawResponse.content.isEmpty { if !rawResponse.content.isEmpty {
let files = rawResponse.content.map { file in let files = rawResponse.content.map { file in
IAFile( DebridIAFile(
fileId: 0,
name: file.path.split(separator: "/").last.flatMap { String($0) } ?? file.path, name: file.path.split(separator: "/").last.flatMap { String($0) } ?? file.path,
streamUrlString: file.link streamUrlString: file.link
) )
} }
return IA( return DebridIA(
magnet: magnet, magnet: magnet,
expiryTimeStamp: Date().timeIntervalSince1970 + 300, expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files files: files
@ -216,6 +216,20 @@ public class Premiumize: OAuthDebridSource {
} }
} }
// Wrapper function to fetch a DDL link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
// Store the item in PM cloud for later use
try await createTransfer(magnet: magnet)
if let iaFile, let streamUrlString = iaFile.streamUrlString {
return streamUrlString
} else if let premiumizeItem = ia, let firstFile = premiumizeItem.files[safe: 0], let streamUrlString = firstFile.streamUrlString {
return streamUrlString
} else {
throw PMError.FailedRequest(description: "Could not fetch your file from the Premiumize API")
}
}
func createTransfer(magnet: Magnet) async throws { func createTransfer(magnet: Magnet) async throws {
guard let magnetLink = magnet.link else { guard let magnetLink = magnet.link else {
throw PMError.FailedRequest(description: "The magnet link is invalid") throw PMError.FailedRequest(description: "The magnet link is invalid")

View file

@ -8,7 +8,6 @@
import Foundation import Foundation
public class RealDebrid: PollingDebridSource { public class RealDebrid: PollingDebridSource {
public let id = "RealDebrid" public let id = "RealDebrid"
public var authTask: Task<Void, Error>? public var authTask: Task<Void, Error>?
@ -87,7 +86,7 @@ public class RealDebrid: PollingDebridSource {
let (data, _) = try await URLSession.shared.data(for: request) let (data, _) = try await URLSession.shared.data(for: request)
// We don't care if this fails // We don't care if this fails
let rawResponse = try? self.jsonDecoder.decode(DeviceCredentialsResponse.self, from: data) let rawResponse = try? jsonDecoder.decode(DeviceCredentialsResponse.self, from: data)
// If there's a client ID from the response, end the task successfully // If there's a client ID from the response, end the task successfully
if let clientId = rawResponse?.clientID, let clientSecret = rawResponse?.clientSecret { if let clientId = rawResponse?.clientID, let clientSecret = rawResponse?.clientSecret {
@ -169,7 +168,7 @@ public class RealDebrid: PollingDebridSource {
return FerriteKeychain.shared.get("RealDebrid.AccessToken") == key return FerriteKeychain.shared.get("RealDebrid.AccessToken") == key
} }
// Deletes tokens from device and RD's servers // Deletes tokens from device and RD's servers
public func logout() async { public func logout() async {
FerriteKeychain.shared.delete("RealDebrid.RefreshToken") FerriteKeychain.shared.delete("RealDebrid.RefreshToken")
@ -213,9 +212,8 @@ public class RealDebrid: PollingDebridSource {
} }
// Checks if the magnet is streamable on RD // Checks if the magnet is streamable on RD
// Currently does not work for batch links public func instantAvailability(magnets: [Magnet]) async throws -> [DebridIA] {
public func instantAvailability(magnets: [Magnet]) async throws -> [IA] { var availableHashes: [DebridIA] = []
var availableHashes: [RealDebrid.IA] = []
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnets.compactMap(\.hash).joined(separator: "/"))")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnets.compactMap(\.hash).joined(separator: "/"))")!)
let data = try await performRequest(request: &request, requestName: #function) let data = try await performRequest(request: &request, requestName: #function)
@ -232,7 +230,7 @@ public class RealDebrid: PollingDebridSource {
continue continue
} }
// Is this a batch // Is this a batch?
if data.rd.count > 1 || data.rd[0].count > 1 { if data.rd.count > 1 || data.rd[0].count > 1 {
// Batch array // Batch array
let batches = data.rd.map { fileDict in let batches = data.rd.map { fileDict in
@ -244,22 +242,18 @@ public class RealDebrid: PollingDebridSource {
return RealDebrid.IABatch(files: batchFiles) return RealDebrid.IABatch(files: batchFiles)
} }
// RD files array var files: [DebridIAFile] = []
// Possibly sort this in the future, but not sure how at the moment
var files: [RealDebrid.IAFile] = []
for index in batches.indices { for batch in batches {
let batchFiles = batches[index].files let batchFileIds = batch.files.map(\.id)
for batchFileIndex in batchFiles.indices { for batchFile in batch.files {
let batchFile = batchFiles[batchFileIndex] if !files.contains(where: { $0.fileId == batchFile.id }) {
if !files.contains(where: { $0.name == batchFile.fileName }) {
files.append( files.append(
RealDebrid.IAFile( DebridIAFile(
fileId: batchFile.id,
name: batchFile.fileName, name: batchFile.fileName,
batchIndex: index, batchIds: batchFileIds
batchFileIndex: batchFileIndex
) )
) )
} }
@ -268,18 +262,18 @@ public class RealDebrid: PollingDebridSource {
// TTL: 5 minutes // TTL: 5 minutes
availableHashes.append( availableHashes.append(
RealDebrid.IA( DebridIA(
magnet: Magnet(hash: hash, link: nil), magnet: Magnet(hash: hash, link: nil),
expiryTimeStamp: Date().timeIntervalSince1970 + 300, expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: files, files: files
batches: batches
) )
) )
} else { } else {
availableHashes.append( availableHashes.append(
RealDebrid.IA( DebridIA(
magnet: Magnet(hash: hash, link: nil), magnet: Magnet(hash: hash, link: nil),
expiryTimeStamp: Date().timeIntervalSince1970 + 300 expiryTimeStamp: Date().timeIntervalSince1970 + 300,
files: []
) )
) )
} }
@ -288,6 +282,21 @@ public class RealDebrid: PollingDebridSource {
return availableHashes return availableHashes
} }
// Wrapper function to fetch a download link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
let selectedMagnetId = try await addMagnet(magnet: magnet)
try await selectFiles(debridID: selectedMagnetId, fileIds: iaFile?.batchIds ?? [])
let torrentLink = try await torrentInfo(
debridID: selectedMagnetId,
selectedIndex: iaFile?.fileId ?? 0
)
let downloadLink = try await unrestrictLink(debridDownloadLink: torrentLink)
return downloadLink
}
// Adds a magnet link to the user's RD account // Adds a magnet link to the user's RD account
public func addMagnet(magnet: Magnet) async throws -> String { public func addMagnet(magnet: Magnet) async throws -> String {
guard let magnetLink = magnet.link else { guard let magnetLink = magnet.link else {
@ -335,9 +344,11 @@ public class RealDebrid: PollingDebridSource {
let data = try await performRequest(request: &request, requestName: #function) let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data) let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data)
let filteredFiles = rawResponse.files.filter { $0.selected == 1 }
let linkIndex = filteredFiles.firstIndex(where: { $0.id == selectedIndex })
// Let the user know if a torrent is downloading // Let the user know if a torrent is downloading
if let torrentLink = rawResponse.links[safe: selectedIndex ?? -1], rawResponse.status == "downloaded" { if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" {
return torrentLink return torrentLink
} else if rawResponse.status == "downloading" || rawResponse.status == "queued" { } else if rawResponse.status == "downloading" || rawResponse.status == "queued" {
throw RDError.EmptyTorrents throw RDError.EmptyTorrents

View file

@ -7,10 +7,24 @@
import Foundation import Foundation
public struct DebridIAFile { public struct DebridIA: Sendable, Hashable {
let magnet: Magnet
let expiryTimeStamp: Double
var files: [DebridIAFile]
} }
public struct DebridCloudFile { public struct DebridIAFile: Hashable, Sendable {
let fileId: Int
let name: String
let streamUrlString: String?
let batchIds: [Int]
init(fileId: Int, name: String, streamUrlString: String? = nil, batchIds: [Int] = []) {
self.fileId = fileId
self.name = name
self.streamUrlString = streamUrlString
self.batchIds = batchIds
}
} }
public struct DebridCloudFile {}

View file

@ -92,13 +92,6 @@ public extension RealDebrid {
// MARK: - Instant Availability client side structures // MARK: - Instant Availability client side structures
struct IA: Codable, Hashable, Sendable {
let magnet: Magnet
let expiryTimeStamp: Double
var files: [IAFile] = []
var batches: [IABatch] = []
}
struct IABatch: Codable, Hashable, Sendable { struct IABatch: Codable, Hashable, Sendable {
let files: [IABatchFile] let files: [IABatchFile]
} }
@ -108,12 +101,6 @@ public extension RealDebrid {
let fileName: String let fileName: String
} }
struct IAFile: Codable, Hashable, Sendable {
let name: String
let batchIndex: Int
let batchFileIndex: Int
}
// MARK: - addMagnet endpoint // MARK: - addMagnet endpoint
struct AddMagnetResponse: Codable, Sendable { struct AddMagnetResponse: Codable, Sendable {

View file

@ -14,6 +14,10 @@ public protocol DebridSource {
// Common authentication functions // Common authentication functions
func setApiKey(_ key: String) -> Bool func setApiKey(_ key: String) -> Bool
func logout() async func logout() async
// Fetches a download link from a source
// Include the instant availability information with the args
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String
} }
public protocol PollingDebridSource: DebridSource { public protocol PollingDebridSource: DebridSource {
@ -25,7 +29,6 @@ public protocol PollingDebridSource: DebridSource {
} }
public protocol OAuthDebridSource: DebridSource { public protocol OAuthDebridSource: DebridSource {
// Fetches the auth URL // Fetches the auth URL
func getAuthUrl() throws -> URL func getAuthUrl() throws -> URL

View file

@ -59,12 +59,12 @@ public class DebridManager: ObservableObject {
var realDebridAuthProcessing: Bool = false var realDebridAuthProcessing: Bool = false
// RealDebrid fetch variables // RealDebrid fetch variables
@Published var realDebridIAValues: [RealDebrid.IA] = [] @Published var realDebridIAValues: [DebridIA] = []
@Published var showDeleteAlert: Bool = false @Published var showDeleteAlert: Bool = false
var selectedRealDebridItem: RealDebrid.IA? var selectedRealDebridItem: DebridIA?
var selectedRealDebridFile: RealDebrid.IAFile? var selectedRealDebridFile: DebridIAFile?
var selectedRealDebridID: String? var selectedRealDebridID: String?
// TODO: Maybe make these generic? // TODO: Maybe make these generic?
@ -77,10 +77,10 @@ public class DebridManager: ObservableObject {
var allDebridAuthProcessing: Bool = false var allDebridAuthProcessing: Bool = false
// AllDebrid fetch variables // AllDebrid fetch variables
@Published var allDebridIAValues: [AllDebrid.IA] = [] @Published var allDebridIAValues: [DebridIA] = []
var selectedAllDebridItem: AllDebrid.IA? var selectedAllDebridItem: DebridIA?
var selectedAllDebridFile: AllDebrid.IAFile? var selectedAllDebridFile: DebridIAFile?
// AllDebrid cloud variables // AllDebrid cloud variables
@Published var allDebridCloudMagnets: [AllDebrid.MagnetStatusData] = [] @Published var allDebridCloudMagnets: [AllDebrid.MagnetStatusData] = []
@ -91,10 +91,10 @@ public class DebridManager: ObservableObject {
var premiumizeAuthProcessing: Bool = false var premiumizeAuthProcessing: Bool = false
// Premiumize fetch variables // Premiumize fetch variables
@Published var premiumizeIAValues: [Premiumize.IA] = [] @Published var premiumizeIAValues: [DebridIA] = []
var selectedPremiumizeItem: Premiumize.IA? var selectedPremiumizeItem: DebridIA?
var selectedPremiumizeFile: Premiumize.IAFile? var selectedPremiumizeFile: DebridIAFile?
// Premiumize cloud variables // Premiumize cloud variables
@Published var premiumizeCloudItems: [Premiumize.UserItem] = [] @Published var premiumizeCloudItems: [Premiumize.UserItem] = []
@ -282,10 +282,10 @@ public class DebridManager: ObservableObject {
return .none return .none
} }
if realDebridMatch.batches.isEmpty { if realDebridMatch.files.count > 1 {
return .full
} else {
return .partial return .partial
} else {
return .full
} }
case .allDebrid: case .allDebrid:
guard let allDebridMatch = allDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) else { guard let allDebridMatch = allDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) else {
@ -578,7 +578,7 @@ public class DebridManager: ObservableObject {
case .allDebrid: case .allDebrid:
await fetchAdDownload(magnet: magnet, existingLockedLink: cloudInfo) await fetchAdDownload(magnet: magnet, existingLockedLink: cloudInfo)
case .premiumize: case .premiumize:
await fetchPmDownload(cloudItemId: cloudInfo) await fetchPmDownload(magnet: magnet, cloudItemId: cloudInfo)
case .none: case .none:
break break
} }
@ -586,6 +586,7 @@ public class DebridManager: ObservableObject {
func fetchRdDownload(magnet: Magnet?, existingLink: String?) async { func fetchRdDownload(magnet: Magnet?, existingLink: String?) async {
// If an existing link is passed in args, set it to that. Otherwise, find one from RD cloud. // If an existing link is passed in args, set it to that. Otherwise, find one from RD cloud.
/*
let torrentLink: String? let torrentLink: String?
if let existingLink { if let existingLink {
torrentLink = existingLink torrentLink = existingLink
@ -596,42 +597,23 @@ public class DebridManager: ObservableObject {
let existingTorrent = realDebridCloudTorrents.first { $0.hash == selectedRealDebridItem?.magnet.hash && $0.status == "downloaded" } let existingTorrent = realDebridCloudTorrents.first { $0.hash == selectedRealDebridItem?.magnet.hash && $0.status == "downloaded" }
torrentLink = existingTorrent?.links[safe: selectedRealDebridFile?.batchFileIndex ?? 0] torrentLink = existingTorrent?.links[safe: selectedRealDebridFile?.batchFileIndex ?? 0]
} }
*/
do { do {
// If the links match from a user's downloads, no need to re-run a download // If the links match from a user's downloads, no need to re-run a download
/*
if let torrentLink, if let torrentLink,
let downloadLink = await checkRdUserDownloads(userTorrentLink: torrentLink) let downloadLink = await checkRdUserDownloads(userTorrentLink: torrentLink)
{ {
downloadUrl = downloadLink downloadUrl = downloadLink
} else if let magnet { } else */
// Add a magnet after all the cache checks fail if let magnet {
selectedRealDebridID = try await realDebrid.addMagnet(magnet: magnet) let downloadLink = try await realDebrid.getDownloadLink(
magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile
)
var fileIds: [Int] = [] // Update the UI
if let iaFile = selectedRealDebridFile { downloadUrl = downloadLink
guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else {
return
}
fileIds = iaBatchFromFile.files.map(\.id)
}
if let realDebridId = selectedRealDebridID {
try await realDebrid.selectFiles(debridID: realDebridId, fileIds: fileIds)
let torrentLink = try await realDebrid.torrentInfo(
debridID: realDebridId,
selectedIndex: selectedRealDebridFile?.batchFileIndex ?? 0
)
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
downloadUrl = downloadLink
} else {
logManager?.error(
"RealDebrid: Could not cache torrent with hash \(String(describing: magnet.hash))",
description: "Could not cache this torrent. Aborting."
)
}
} else { } else {
throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API") throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API")
} }
@ -645,7 +627,7 @@ public class DebridManager: ObservableObject {
default: default:
await sendDebridError(error, prefix: "RealDebrid download error", cancelString: "Download cancelled") await sendDebridError(error, prefix: "RealDebrid download error", cancelString: "Download cancelled")
await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false) // await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false)
} }
logManager?.hideIndeterminateToast() logManager?.hideIndeterminateToast()
@ -695,8 +677,6 @@ public class DebridManager: ObservableObject {
do { do {
if let torrentID { if let torrentID {
try await realDebrid.deleteTorrent(debridID: torrentID) try await realDebrid.deleteTorrent(debridID: torrentID)
} else if let selectedTorrentID = selectedRealDebridID {
try await realDebrid.deleteTorrent(debridID: selectedTorrentID)
} else { } else {
throw RealDebrid.RDError.FailedRequest(description: "No torrent ID was provided") throw RealDebrid.RDError.FailedRequest(description: "No torrent ID was provided")
} }
@ -720,34 +700,36 @@ public class DebridManager: ObservableObject {
} }
} }
// TODO: Integrate with AD saved links
func fetchAdDownload(magnet: Magnet?, existingLockedLink: String?) async { func fetchAdDownload(magnet: Magnet?, existingLockedLink: String?) async {
// If an existing link is passed in args, set it to that. Otherwise, find one from AD cloud. // If an existing link is passed in args, set it to that. Otherwise, find one from AD cloud.
let lockedLink: String? /*
if let existingLockedLink { let lockedLink: String?
lockedLink = existingLockedLink if let existingLockedLink {
} else { lockedLink = existingLockedLink
// Bypass the TTL for up to date information } else {
await fetchAdCloud(bypassTTL: true) // Bypass the TTL for up to date information
await fetchAdCloud(bypassTTL: true)
let existingMagnet = allDebridCloudMagnets.first { $0.hash == selectedAllDebridItem?.magnet.hash && $0.status == "Ready" } let existingMagnet = allDebridCloudMagnets.first { $0.hash == selectedAllDebridItem?.magnet.hash && $0.status == "Ready" }
lockedLink = existingMagnet?.links[safe: selectedAllDebridFile?.id ?? 0]?.link lockedLink = existingMagnet?.links[safe: selectedAllDebridFile?.fileId ?? 0]?.link
} }
*/
do { do {
if let lockedLink, /*
let unlockedLink = await checkAdUserLinks(lockedLink: lockedLink) if let lockedLink,
{ let unlockedLink = await checkAdUserLinks(lockedLink: lockedLink)
downloadUrl = unlockedLink {
} else if let magnet { downloadUrl = unlockedLink
let magnetID = try await allDebrid.addMagnet(magnet: magnet) } else if let magnet {
let lockedLink = try await allDebrid.fetchMagnetStatus( */
magnetId: magnetID, if let magnet {
selectedIndex: selectedAllDebridFile?.id ?? 0 let downloadLink = try await allDebrid.getDownloadLink(
magnet: magnet, ia: selectedAllDebridItem, iaFile: selectedAllDebridFile
) )
try await allDebrid.saveLink(link: lockedLink) // Update UI
downloadUrl = try await allDebrid.unlockLink(lockedLink: lockedLink) downloadUrl = downloadLink
} else { } else {
throw AllDebrid.ADError.FailedRequest(description: "Could not fetch your file from AllDebrid's cache or API") throw AllDebrid.ADError.FailedRequest(description: "Could not fetch your file from AllDebrid's cache or API")
} }
@ -810,28 +792,22 @@ public class DebridManager: ObservableObject {
} }
} }
func fetchPmDownload(cloudItemId: String? = nil) async { func fetchPmDownload(magnet: Magnet?, cloudItemId: String? = nil) async {
do { do {
if let cloudItemId { if let cloudItemId {
downloadUrl = try await premiumize.itemDetails(itemID: cloudItemId).link downloadUrl = try await premiumize.itemDetails(itemID: cloudItemId).link
} else if let premiumizeFile = selectedPremiumizeFile { } else if let magnet {
downloadUrl = premiumizeFile.streamUrlString let downloadLink = try await premiumize.getDownloadLink(
} else if magnet: magnet, ia: selectedPremiumizeItem, iaFile: selectedPremiumizeFile
let premiumizeItem = selectedPremiumizeItem, )
let firstFile = premiumizeItem.files[safe: 0]
{ downloadUrl = downloadLink
downloadUrl = firstFile.streamUrlString
} else { } else {
throw Premiumize.PMError.FailedRequest(description: "There were no items or files found!") throw Premiumize.PMError.FailedRequest(description: "Could not fetch your file from Premiumize's cache or API")
} }
// Fetch one more time to add updated data into the PM cloud cache // Fetch one more time to add updated data into the PM cloud cache
await fetchPmCloud(bypassTTL: true) await fetchPmCloud(bypassTTL: true)
// Add a PM transfer if the item exists
if let premiumizeItem = selectedPremiumizeItem {
try await premiumize.createTransfer(magnet: premiumizeItem.magnet)
}
} catch { } catch {
await sendDebridError(error, prefix: "Premiumize download error", cancelString: "Download or transfer cancelled") await sendDebridError(error, prefix: "Premiumize download error", cancelString: "Download or transfer cancelled")
} }

View file

@ -21,7 +21,7 @@ struct HybridSecureField: View {
private var isFieldDisabled: Bool = false private var isFieldDisabled: Bool = false
init(text: Binding<String>, onCommit: (() -> Void)? = nil, showPassword: Bool = false) { init(text: Binding<String>, onCommit: (() -> Void)? = nil, showPassword: Bool = false) {
self._text = text _text = text
if let onCommit { if let onCommit {
self.onCommit = onCommit self.onCommit = onCommit
} }
@ -57,6 +57,6 @@ struct HybridSecureField: View {
extension HybridSecureField { extension HybridSecureField {
public func fieldDisabled(_ isFieldDisabled: Bool) -> Self { public func fieldDisabled(_ isFieldDisabled: Bool) -> Self {
modifyViewProp({ $0.isFieldDisabled = isFieldDisabled }) modifyViewProp { $0.isFieldDisabled = isFieldDisabled }
} }
} }

View file

@ -96,7 +96,7 @@ struct SettingsView: View {
if changed { if changed {
Task { Task {
let dataRecords = await WKWebsiteDataStore.default().dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) let dataRecords = await WKWebsiteDataStore.default().dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes())
await WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: dataRecords) await WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: dataRecords)
} }
} }

View file

@ -36,11 +36,11 @@ struct BatchChoiceView: View {
} }
case .allDebrid: case .allDebrid:
ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in
if file.fileName.lowercased().contains(searchText.lowercased()) || searchText.isEmpty { if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
Button(file.fileName) { Button(file.name) {
debridManager.selectedAllDebridFile = file debridManager.selectedAllDebridFile = file
queueCommonDownload(fileName: file.fileName) queueCommonDownload(fileName: file.name)
} }
} }
} }