diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 061b6c0..ded65f3 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -12,6 +12,9 @@ 0C03EB72296F619900162E9A /* PluginList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03EB70296F619900162E9A /* PluginList+CoreDataProperties.swift */; }; 0C0755C6293424A200ECA142 /* DebridLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0755C5293424A200ECA142 /* DebridLabelView.swift */; }; 0C0755C8293425B500ECA142 /* DebridManagerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0755C7293425B500ECA142 /* DebridManagerModels.swift */; }; + 0C07C6002C19FEBF00808A46 /* OffCloudWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C07C5FF2C19FEBF00808A46 /* OffCloudWrapper.swift */; }; + 0C07C6022C1A016B00808A46 /* OffCloudModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C07C6012C1A016B00808A46 /* OffCloudModels.swift */; }; + 0C07C6042C1A859B00808A46 /* FormDataBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C07C6032C1A859B00808A46 /* FormDataBody.swift */; }; 0C0974B029CCAAAF006DE7A3 /* OperatingSystemVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0974AF29CCAAAF006DE7A3 /* OperatingSystemVersion.swift */; }; 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; }; 0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */; }; @@ -94,6 +97,8 @@ 0C84FCE729E4B61A00B0DFE4 /* FilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */; }; 0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; }; 0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.swift */; }; + 0C890E492C188808003B17B5 /* TorBoxWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C890E482C188808003B17B5 /* TorBoxWrapper.swift */; }; + 0C890E4B2C188FA7003B17B5 /* TorBoxModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C890E4A2C188FA7003B17B5 /* TorBoxModels.swift */; }; 0C8AE2482C0FFB6600701675 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8AE2472C0FFB6600701675 /* Store.swift */; }; 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */; }; 0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */; }; @@ -167,6 +172,9 @@ 0C03EB70296F619900162E9A /* PluginList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginList+CoreDataProperties.swift"; sourceTree = ""; }; 0C0755C5293424A200ECA142 /* DebridLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridLabelView.swift; sourceTree = ""; }; 0C0755C7293425B500ECA142 /* DebridManagerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridManagerModels.swift; sourceTree = ""; }; + 0C07C5FF2C19FEBF00808A46 /* OffCloudWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffCloudWrapper.swift; sourceTree = ""; }; + 0C07C6012C1A016B00808A46 /* OffCloudModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffCloudModels.swift; sourceTree = ""; }; + 0C07C6032C1A859B00808A46 /* FormDataBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormDataBody.swift; sourceTree = ""; }; 0C0974AF29CCAAAF006DE7A3 /* OperatingSystemVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersion.swift; sourceTree = ""; }; 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = ""; }; 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginAggregateView.swift; sourceTree = ""; }; @@ -244,6 +252,8 @@ 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterModels.swift; sourceTree = ""; }; 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterAmountLabelView.swift; sourceTree = ""; }; 0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = ""; }; + 0C890E482C188808003B17B5 /* TorBoxWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBoxWrapper.swift; sourceTree = ""; }; + 0C890E4A2C188FA7003B17B5 /* TorBoxModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBoxModels.swift; sourceTree = ""; }; 0C8AE2472C0FFB6600701675 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInfoView.swift; sourceTree = ""; }; 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = ""; }; @@ -410,6 +420,8 @@ 0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */, 0C6771F929B3D1AE005D38D2 /* KodiModels.swift */, 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */, + 0C890E4A2C188FA7003B17B5 /* TorBoxModels.swift */, + 0C07C6012C1A016B00808A46 /* OffCloudModels.swift */, ); path = Models; sourceTree = ""; @@ -463,6 +475,7 @@ 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */, 0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */, 0C8AE2472C0FFB6600701675 /* Store.swift */, + 0C07C6032C1A859B00808A46 /* FormDataBody.swift */, ); path = Utils; sourceTree = ""; @@ -661,6 +674,8 @@ 0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */, 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */, 0C6771F329B3B4FD005D38D2 /* KodiWrapper.swift */, + 0C890E482C188808003B17B5 /* TorBoxWrapper.swift */, + 0C07C5FF2C19FEBF00808A46 /* OffCloudWrapper.swift */, ); path = API; sourceTree = ""; @@ -829,9 +844,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0C07C6042C1A859B00808A46 /* FormDataBody.swift in Sources */, 0C7ED14328D65518009E29AD /* FileManager.swift in Sources */, 0C03EB71296F619900162E9A /* PluginList+CoreDataClass.swift in Sources */, 0C6771FA29B3D1AE005D38D2 /* KodiModels.swift in Sources */, + 0C07C6002C19FEBF00808A46 /* OffCloudWrapper.swift in Sources */, 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */, 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */, 0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */, @@ -927,6 +944,7 @@ 0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */, 0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */, 0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */, + 0C07C6022C1A016B00808A46 /* OffCloudModels.swift in Sources */, 0C78041D28BFB3EA001E8CA3 /* String.swift in Sources */, 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */, 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, @@ -940,6 +958,7 @@ 0C6771F429B3B4FD005D38D2 /* KodiWrapper.swift in Sources */, 0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */, 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */, + 0C890E4B2C188FA7003B17B5 /* TorBoxModels.swift in Sources */, 0C7075E429D374C50093DB2D /* Color.swift in Sources */, 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */, 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */, @@ -956,6 +975,7 @@ 0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */, 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */, 0C6771F629B3B602005D38D2 /* SettingsKodiView.swift in Sources */, + 0C890E492C188808003B17B5 /* TorBoxWrapper.swift in Sources */, 0CF1ABDC2C0C04B2009F6C26 /* Debrid.swift in Sources */, 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */, 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */, @@ -1035,6 +1055,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; }; name = Debug; }; @@ -1090,6 +1111,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_STRICT_CONCURRENCY = minimal; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/Ferrite/API/AllDebridWrapper.swift b/Ferrite/API/AllDebridWrapper.swift index 438bdac..033331c 100644 --- a/Ferrite/API/AllDebridWrapper.swift +++ b/Ferrite/API/AllDebridWrapper.swift @@ -214,7 +214,7 @@ class AllDebrid: PollingDebridSource, ObservableObject { // MARK: - Downloading // Wrapper function to fetch a download link from the API - func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { + func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) { let selectedMagnetId: String if let existingMagnet = cloudTorrents.first(where: { $0.hash == magnet.hash && $0.status == "Ready" }) { @@ -229,10 +229,7 @@ class AllDebrid: PollingDebridSource, ObservableObject { selectedIndex: iaFile?.fileId ?? 0 ) - try await saveLink(link: lockedLink) - let downloadUrl = try await unlockLink(lockedLink: lockedLink) - - return downloadUrl + return (lockedLink, nil) } // Adds a magnet link to the user's AD account @@ -262,7 +259,7 @@ class AllDebrid: PollingDebridSource, ObservableObject { } } - func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> String { + func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> DebridIAFile { let queryItems = [ URLQueryItem(name: "id", value: magnetId) ] @@ -272,20 +269,21 @@ class AllDebrid: PollingDebridSource, ObservableObject { let rawResponse = try jsonDecoder.decode(ADResponse.self, from: data).data // Better to fetch no link at all than the wrong link - if let linkWrapper = rawResponse.magnets[safe: 0]?.links[safe: selectedIndex ?? -1] { - return linkWrapper.link + if let torrentFile = rawResponse.magnets[safe: 0]?.links[safe: selectedIndex ?? -1] { + return DebridIAFile(fileId: 0, name: torrentFile.filename, streamUrlString: torrentFile.link) } else { throw DebridError.EmptyTorrents } } - func unlockLink(lockedLink: String) async throws -> String { + // Known as unlockLink in AD's API + func unrestrictFile(_ restrictedFile: DebridIAFile) async throws -> String { let queryItems = [ - URLQueryItem(name: "link", value: lockedLink) + URLQueryItem(name: "link", value: restrictedFile.streamUrlString) ] var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/link/unlock", queryItems: queryItems)) - let data = try await performRequest(request: &request, requestName: #function) + let data = try await performRequest(request: &request, requestName: "unlockLink") let rawResponse = try jsonDecoder.decode(ADResponse.self, from: data).data return rawResponse.link diff --git a/Ferrite/API/PremiumizeWrapper.swift b/Ferrite/API/PremiumizeWrapper.swift index 12e7b02..9fafa4c 100644 --- a/Ferrite/API/PremiumizeWrapper.swift +++ b/Ferrite/API/PremiumizeWrapper.swift @@ -277,20 +277,27 @@ class Premiumize: OAuthDebridSource, ObservableObject { // MARK: - Downloading - // Wrapper function to fetch a DDL link from the API - func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { + func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) { // 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 + if let iaFile { + return (iaFile, nil) + } else if let premiumizeItem = ia, let firstFile = premiumizeItem.files[safe: 0] { + return (firstFile, nil) } else { throw DebridError.FailedRequest(description: "Could not fetch your file from the Premiumize API") } } + func unrestrictFile(_ restrictedFile: DebridIAFile) async throws -> String { + guard let streamUrlString = restrictedFile.streamUrlString else { + throw DebridError.FailedRequest(description: "Could not get a streaming URL from the Premiumize API") + } + + return streamUrlString + } + private func createTransfer(magnet: Magnet) async throws { guard let magnetLink = magnet.link else { throw DebridError.FailedRequest(description: "The magnet link is invalid") diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 08f69ee..975f8a2 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -273,62 +273,50 @@ class RealDebrid: PollingDebridSource, ObservableObject { continue } - // Is this a batch? - if data.rd.count > 1 || data.rd[0].count > 1 { - // Batch array - let batches = data.rd.map { fileDict in - let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in - // Force unwrapped ID. Is safe because ID is guaranteed on a successful response - RealDebrid.IABatchFile(id: Int(key)!, fileName: value.filename) - }.sorted(by: { $0.id < $1.id }) + // Handle files array + let batches = data.rd.map { fileDict in + let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in + // Force unwrapped ID. Is safe because ID is guaranteed on a successful response + RealDebrid.IABatchFile(id: Int(key)!, fileName: value.filename) + }.sorted(by: { $0.id < $1.id }) - return RealDebrid.IABatch(files: batchFiles) - } + return RealDebrid.IABatch(files: batchFiles) + } - var files: [DebridIAFile] = [] + var files: [DebridIAFile] = [] - for batch in batches { - let batchFileIds = batch.files.map(\.id) + for batch in batches { + let batchFileIds = batch.files.map(\.id) - for batchFile in batch.files { - if !files.contains(where: { $0.fileId == batchFile.id }) { - files.append( - DebridIAFile( - fileId: batchFile.id, - name: batchFile.fileName, - batchIds: batchFileIds - ) + for batchFile in batch.files { + if !files.contains(where: { $0.fileId == batchFile.id }) { + files.append( + DebridIAFile( + fileId: batchFile.id, + name: batchFile.fileName, + batchIds: batchFileIds ) - } + ) } } - - // TTL: 5 minutes - IAValues.append( - DebridIA( - magnet: Magnet(hash: hash, link: nil), - source: id, - expiryTimeStamp: Date().timeIntervalSince1970 + 300, - files: files - ) - ) - } else { - IAValues.append( - DebridIA( - magnet: Magnet(hash: hash, link: nil), - source: id, - expiryTimeStamp: Date().timeIntervalSince1970 + 300, - files: [] - ) - ) } + + // TTL: 5 minutes + IAValues.append( + DebridIA( + magnet: Magnet(hash: hash, link: nil), + source: id, + expiryTimeStamp: Date().timeIntervalSince1970 + 300, + files: files + ) + ) } } // MARK: - Downloading // Wrapper function to fetch a download link from the API - func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { + func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) { var selectedMagnetId = "" do { @@ -342,13 +330,12 @@ class RealDebrid: PollingDebridSource, ObservableObject { } // RealDebrid has 1 as the first ID for a file - let torrentLink = try await torrentInfo( + let torrentFile = try await torrentInfo( debridID: selectedMagnetId, selectedFileId: iaFile?.fileId ?? 1 ) - let downloadLink = try await unrestrictLink(debridDownloadLink: torrentLink) - return downloadLink + return (torrentFile, nil) } catch { if case DebridError.EmptyTorrents = error, !selectedMagnetId.isEmpty { try? await deleteTorrent(torrentId: selectedMagnetId) @@ -401,7 +388,7 @@ class RealDebrid: PollingDebridSource, ObservableObject { } // Gets the info of a torrent from a given ID - func torrentInfo(debridID: String, selectedFileId: Int?) async throws -> String { + func torrentInfo(debridID: String, selectedFileId: Int?) async throws -> DebridIAFile { var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!) let data = try await performRequest(request: &request, requestName: #function) @@ -411,7 +398,11 @@ class RealDebrid: PollingDebridSource, ObservableObject { // Let the user know if a torrent is downloading if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" { - return torrentLink + return DebridIAFile( + fileId: 0, + name: rawResponse.filename, + streamUrlString: torrentLink + ) } else if rawResponse.status == "downloading" || rawResponse.status == "queued" { throw DebridError.IsCaching } else { @@ -420,13 +411,13 @@ class RealDebrid: PollingDebridSource, ObservableObject { } // Downloads link from selectFiles for playback - func unrestrictLink(debridDownloadLink: String) async throws -> String { + func unrestrictFile(_ restrictedFile: DebridIAFile) async throws -> String { var request = URLRequest(url: URL(string: "\(baseApiUrl)/unrestrict/link")!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") var bodyComponents = URLComponents() - bodyComponents.queryItems = [URLQueryItem(name: "link", value: debridDownloadLink)] + bodyComponents.queryItems = [URLQueryItem(name: "link", value: restrictedFile.streamUrlString)] request.httpBody = bodyComponents.query?.data(using: .utf8) diff --git a/Ferrite/Protocols/Debrid.swift b/Ferrite/Protocols/Debrid.swift index 2cf867f..0c396d8 100644 --- a/Ferrite/Protocols/Debrid.swift +++ b/Ferrite/Protocols/Debrid.swift @@ -39,7 +39,10 @@ protocol DebridSource: AnyObservableObject { // Fetches a download link from a source // Include the instant availability information with the args // Torrents also checked here - func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String + func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) + + // Unrestricts a locked file + func unrestrictFile(_ restrictedFile: DebridIAFile) async throws -> String // User downloads functions func getUserDownloads() async throws diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index b5d03d2..962a721 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -39,6 +39,7 @@ class DebridManager: ObservableObject { var selectedDebridItem: DebridIA? var selectedDebridFile: DebridIAFile? + var requiresUnrestrict: Bool = false // TODO: Figure out a way to remove this var private var selectedOAuthDebridSource: OAuthDebridSource? @@ -167,6 +168,11 @@ class DebridManager: ObservableObject { if let IAItem = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash }) { selectedDebridItem = IAItem + + if IAItem.files.count == 1 { + selectedDebridFile = IAItem.files[safe: 0] + } + return true } else { logManager?.error("DebridManager: Could not find the associated \(selectedSource.id) entry for magnet hash \(magnetHash)") @@ -311,8 +317,8 @@ class DebridManager: ObservableObject { // Cloudinfo is used for any extra information provided by debrid cloud func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async { defer { - currentDebridTask = nil logManager?.hideIndeterminateToast() + currentDebridTask = nil } logManager?.updateIndeterminateToast("Loading content", cancelAction: { @@ -331,12 +337,24 @@ class DebridManager: ObservableObject { } if let magnet { - let downloadLink = try await debridSource.getDownloadLink( + let (restrictedFile, newIA) = try await debridSource.getRestrictedFile( magnet: magnet, ia: selectedDebridItem, iaFile: selectedDebridFile ) + // Indicate that a link needs to be selected (batch) + if let newIA { + selectedDebridItem = newIA + requiresUnrestrict = true + + return + } + + guard let restrictedFile else { + throw DebridError.FailedRequest(description: "No files found for your request") + } + // Update the UI - downloadUrl = downloadLink + downloadUrl = try await debridSource.unrestrictFile(restrictedFile) } else { throw DebridError.FailedRequest(description: "Could not fetch your file from \(debridSource.id)'s cache or API") } @@ -350,8 +368,34 @@ class DebridManager: ObservableObject { default: await sendDebridError(error, prefix: "\(debridSource.id) download error", cancelString: "Download cancelled") } + } + return + } + + func unrestrictDownload() async { + defer { logManager?.hideIndeterminateToast() + requiresUnrestrict = false + } + + logManager?.updateIndeterminateToast("Loading content", cancelAction: { + self.currentDebridTask?.cancel() + self.currentDebridTask = nil + }) + + guard let debridFile = selectedDebridFile, let debridSource = selectedDebridSource else { + logManager?.error("DebridManager: Could not unrestrict the selected debrid file.") + + return + } + + do { + let downloadLink = try await debridSource.unrestrictFile(debridFile) + + downloadUrl = downloadLink + } catch { + await sendDebridError(error, prefix: "\(debridSource.id) unrestrict error", cancelString: "Unrestrict cancelled") } } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/CloudTorrentView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/CloudTorrentView.swift index cdc950f..07acfc1 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/CloudTorrentView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/CloudTorrentView.swift @@ -40,6 +40,14 @@ struct CloudTorrentView: View { if cloudTorrent.links.count == 1 { await debridManager.fetchDebridDownload(magnet: magnet) + // Bump to batch + if debridManager.requiresUnrestrict { + navModel.selectedHistoryInfo = historyInfo + navModel.currentChoiceSheet = .batch + + return + } + if !debridManager.downloadUrl.isEmpty { historyInfo.url = debridManager.downloadUrl PersistenceController.shared.createHistory(historyInfo, performSave: true) diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift index fa2d24e..0e94938 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift @@ -32,17 +32,23 @@ struct SearchResultButtonView: View { case .full: if debridManager.selectDebridResult(magnet: result.magnet) { debridManager.currentDebridTask = Task { + let historyEntry = HistoryEntryJson( + name: result.title, + url: debridManager.downloadUrl, + source: result.source + ) await debridManager.fetchDebridDownload(magnet: result.magnet) + // Bump to batch + if debridManager.requiresUnrestrict { + navModel.selectedHistoryInfo = historyEntry + navModel.currentChoiceSheet = .batch + + return + } + if !debridManager.downloadUrl.isEmpty { - PersistenceController.shared.createHistory( - HistoryEntryJson( - name: result.title, - url: debridManager.downloadUrl, - source: result.source - ), - performSave: true - ) + PersistenceController.shared.createHistory(historyEntry, performSave: true) pluginManager.runDefaultAction( urlString: debridManager.downloadUrl, @@ -133,9 +139,9 @@ struct SearchResultButtonView: View { Button("Cancel", role: .cancel) {} } message: { Text( - "\(String(describing: debridManager.selectedDebridSource?.id)) is currently caching this file. " + + "\(debridManager.selectedDebridSource?.id ?? "Unknown Debrid") is currently caching this file. " + "Would you like to delete it? \n\n" + - "Progress can be checked on the RealDebrid website." + "Progress can be checked on the \(debridManager.selectedDebridSource?.id ?? "Unknown Debrid") website." ) } .onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { notification in diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index 1620fdc..81d7dca 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -60,7 +60,11 @@ struct BatchChoiceView: View { // Common function to communicate betwen VMs and queue/display a download func queueCommonDownload(fileName: String) { debridManager.currentDebridTask = Task { - await debridManager.fetchDebridDownload(magnet: navModel.selectedMagnet) + if debridManager.requiresUnrestrict { + await debridManager.unrestrictDownload() + } else { + await debridManager.fetchDebridDownload(magnet: navModel.selectedMagnet) + } if !debridManager.downloadUrl.isEmpty { try? await Task.sleep(seconds: 1)