Debrid: Add split for download and unrestrict

Some debrid services aren't "rich", which means that they don't
broadcast whether an instantly available torrent is a batch or a
single file. This results in all torrents either having the green
badge or red badge based on what hash is given.

However, batches need to intercept the download itself which requires
the download function to be split into download and unrestrict. In
between, there's room for the batch sheet to act.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2024-06-12 23:55:12 -04:00 committed by Brian Dashore
parent e46e115244
commit 0f69b92540
9 changed files with 164 additions and 81 deletions

View file

@ -12,6 +12,9 @@
0C03EB72296F619900162E9A /* PluginList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03EB70296F619900162E9A /* PluginList+CoreDataProperties.swift */; }; 0C03EB72296F619900162E9A /* PluginList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03EB70296F619900162E9A /* PluginList+CoreDataProperties.swift */; };
0C0755C6293424A200ECA142 /* DebridLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0755C5293424A200ECA142 /* DebridLabelView.swift */; }; 0C0755C6293424A200ECA142 /* DebridLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0755C5293424A200ECA142 /* DebridLabelView.swift */; };
0C0755C8293425B500ECA142 /* DebridManagerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0755C7293425B500ECA142 /* DebridManagerModels.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 */; }; 0C0974B029CCAAAF006DE7A3 /* OperatingSystemVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0974AF29CCAAAF006DE7A3 /* OperatingSystemVersion.swift */; };
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; }; 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; };
0C0D50E7288DFF850035ECC8 /* PluginAggregateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.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 */; }; 0C84FCE729E4B61A00B0DFE4 /* FilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */; };
0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; }; 0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */; };
0C871BDF29994D9D005279AC /* FilterLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C871BDE29994D9D005279AC /* FilterLabelView.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 */; }; 0C8AE2482C0FFB6600701675 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8AE2472C0FFB6600701675 /* Store.swift */; };
0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */; }; 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */; };
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.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 = "<group>"; }; 0C03EB70296F619900162E9A /* PluginList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginList+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C0755C5293424A200ECA142 /* DebridLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridLabelView.swift; sourceTree = "<group>"; }; 0C0755C5293424A200ECA142 /* DebridLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridLabelView.swift; sourceTree = "<group>"; };
0C0755C7293425B500ECA142 /* DebridManagerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridManagerModels.swift; sourceTree = "<group>"; }; 0C0755C7293425B500ECA142 /* DebridManagerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridManagerModels.swift; sourceTree = "<group>"; };
0C07C5FF2C19FEBF00808A46 /* OffCloudWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffCloudWrapper.swift; sourceTree = "<group>"; };
0C07C6012C1A016B00808A46 /* OffCloudModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffCloudModels.swift; sourceTree = "<group>"; };
0C07C6032C1A859B00808A46 /* FormDataBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormDataBody.swift; sourceTree = "<group>"; };
0C0974AF29CCAAAF006DE7A3 /* OperatingSystemVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersion.swift; sourceTree = "<group>"; }; 0C0974AF29CCAAAF006DE7A3 /* OperatingSystemVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersion.swift; sourceTree = "<group>"; };
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; }; 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; };
0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginAggregateView.swift; sourceTree = "<group>"; }; 0C0D50E6288DFF850035ECC8 /* PluginAggregateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginAggregateView.swift; sourceTree = "<group>"; };
@ -244,6 +252,8 @@
0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterModels.swift; sourceTree = "<group>"; }; 0C84FCE629E4B61A00B0DFE4 /* FilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterModels.swift; sourceTree = "<group>"; };
0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterAmountLabelView.swift; sourceTree = "<group>"; }; 0C84FCE829E5ADEF00B0DFE4 /* FilterAmountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterAmountLabelView.swift; sourceTree = "<group>"; };
0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = "<group>"; }; 0C871BDE29994D9D005279AC /* FilterLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterLabelView.swift; sourceTree = "<group>"; };
0C890E482C188808003B17B5 /* TorBoxWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBoxWrapper.swift; sourceTree = "<group>"; };
0C890E4A2C188FA7003B17B5 /* TorBoxModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorBoxModels.swift; sourceTree = "<group>"; };
0C8AE2472C0FFB6600701675 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; }; 0C8AE2472C0FFB6600701675 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
0C8DC35129CE287E008A83AD /* PluginInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInfoView.swift; sourceTree = "<group>"; }; 0C8DC35129CE287E008A83AD /* PluginInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInfoView.swift; sourceTree = "<group>"; };
0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = "<group>"; }; 0C8DC35329CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSettingsBaseUrlView.swift; sourceTree = "<group>"; };
@ -410,6 +420,8 @@
0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */, 0C3E00D7296F5B9A00ECECB2 /* PluginModels.swift */,
0C6771F929B3D1AE005D38D2 /* KodiModels.swift */, 0C6771F929B3D1AE005D38D2 /* KodiModels.swift */,
0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */, 0C1A3E5129C8A7F500DA9730 /* SettingsModels.swift */,
0C890E4A2C188FA7003B17B5 /* TorBoxModels.swift */,
0C07C6012C1A016B00808A46 /* OffCloudModels.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -463,6 +475,7 @@
0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */, 0C1A3E5529C9488C00DA9730 /* CodableWrapper.swift */,
0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */, 0CD0265629FEFBF900A83D25 /* FerriteKeychain.swift */,
0C8AE2472C0FFB6600701675 /* Store.swift */, 0C8AE2472C0FFB6600701675 /* Store.swift */,
0C07C6032C1A859B00808A46 /* FormDataBody.swift */,
); );
path = Utils; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
@ -661,6 +674,8 @@
0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */, 0C422E7D293542EA00486D65 /* PremiumizeWrapper.swift */,
0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */, 0CA148D0288903F000DE2211 /* RealDebridWrapper.swift */,
0C6771F329B3B4FD005D38D2 /* KodiWrapper.swift */, 0C6771F329B3B4FD005D38D2 /* KodiWrapper.swift */,
0C890E482C188808003B17B5 /* TorBoxWrapper.swift */,
0C07C5FF2C19FEBF00808A46 /* OffCloudWrapper.swift */,
); );
path = API; path = API;
sourceTree = "<group>"; sourceTree = "<group>";
@ -829,9 +844,11 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0C07C6042C1A859B00808A46 /* FormDataBody.swift in Sources */,
0C7ED14328D65518009E29AD /* FileManager.swift in Sources */, 0C7ED14328D65518009E29AD /* FileManager.swift in Sources */,
0C03EB71296F619900162E9A /* PluginList+CoreDataClass.swift in Sources */, 0C03EB71296F619900162E9A /* PluginList+CoreDataClass.swift in Sources */,
0C6771FA29B3D1AE005D38D2 /* KodiModels.swift in Sources */, 0C6771FA29B3D1AE005D38D2 /* KodiModels.swift in Sources */,
0C07C6002C19FEBF00808A46 /* OffCloudWrapper.swift in Sources */,
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */, 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */, 0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */,
0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */, 0C8DC35429CE2AB5008A83AD /* SourceSettingsBaseUrlView.swift in Sources */,
@ -927,6 +944,7 @@
0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */, 0C84FCE929E5ADEF00B0DFE4 /* FilterAmountLabelView.swift in Sources */,
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */, 0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */,
0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */, 0CA05457288EE58200850554 /* SettingsPluginListView.swift in Sources */,
0C07C6022C1A016B00808A46 /* OffCloudModels.swift in Sources */,
0C78041D28BFB3EA001E8CA3 /* String.swift in Sources */, 0C78041D28BFB3EA001E8CA3 /* String.swift in Sources */,
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */, 0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */,
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */,
@ -940,6 +958,7 @@
0C6771F429B3B4FD005D38D2 /* KodiWrapper.swift in Sources */, 0C6771F429B3B4FD005D38D2 /* KodiWrapper.swift in Sources */,
0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */, 0C3E00D0296F4DB200ECECB2 /* ActionModels.swift in Sources */,
0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */, 0C44E2AD28D51C63007711AE /* BackupManager.swift in Sources */,
0C890E4B2C188FA7003B17B5 /* TorBoxModels.swift in Sources */,
0C7075E429D374C50093DB2D /* Color.swift in Sources */, 0C7075E429D374C50093DB2D /* Color.swift in Sources */,
0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */, 0C8DC35229CE287E008A83AD /* PluginInfoView.swift in Sources */,
0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */, 0C422E80293542F300486D65 /* PremiumizeModels.swift in Sources */,
@ -956,6 +975,7 @@
0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */, 0CE1C4182981E8D700418F20 /* Plugin.swift in Sources */,
0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */, 0CEC8AB2299B3B57007BFE8F /* LibraryPickerView.swift in Sources */,
0C6771F629B3B602005D38D2 /* SettingsKodiView.swift in Sources */, 0C6771F629B3B602005D38D2 /* SettingsKodiView.swift in Sources */,
0C890E492C188808003B17B5 /* TorBoxWrapper.swift in Sources */,
0CF1ABDC2C0C04B2009F6C26 /* Debrid.swift in Sources */, 0CF1ABDC2C0C04B2009F6C26 /* Debrid.swift in Sources */,
0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */, 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */,
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */, 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */,
@ -1035,6 +1055,7 @@
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
}; };
name = Debug; name = Debug;
}; };
@ -1090,6 +1111,7 @@
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_STRICT_CONCURRENCY = minimal;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;

View file

@ -214,7 +214,7 @@ class AllDebrid: PollingDebridSource, ObservableObject {
// MARK: - Downloading // MARK: - Downloading
// Wrapper function to fetch a download link from the API // 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 let selectedMagnetId: String
if let existingMagnet = cloudTorrents.first(where: { $0.hash == magnet.hash && $0.status == "Ready" }) { 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 selectedIndex: iaFile?.fileId ?? 0
) )
try await saveLink(link: lockedLink) return (lockedLink, nil)
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
@ -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 = [ let queryItems = [
URLQueryItem(name: "id", value: magnetId) URLQueryItem(name: "id", value: magnetId)
] ]
@ -272,20 +269,21 @@ class AllDebrid: PollingDebridSource, ObservableObject {
let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<MagnetStatusResponse>.self, from: data).data
// Better to fetch no link at all than the wrong link // Better to fetch no link at all than the wrong link
if let linkWrapper = rawResponse.magnets[safe: 0]?.links[safe: selectedIndex ?? -1] { if let torrentFile = rawResponse.magnets[safe: 0]?.links[safe: selectedIndex ?? -1] {
return linkWrapper.link return DebridIAFile(fileId: 0, name: torrentFile.filename, streamUrlString: torrentFile.link)
} else { } else {
throw DebridError.EmptyTorrents 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 = [ 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)) 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<UnlockLinkResponse>.self, from: data).data let rawResponse = try jsonDecoder.decode(ADResponse<UnlockLinkResponse>.self, from: data).data
return rawResponse.link return rawResponse.link

View file

@ -277,20 +277,27 @@ class Premiumize: OAuthDebridSource, ObservableObject {
// MARK: - Downloading // MARK: - Downloading
// Wrapper function to fetch a DDL link from the API func getRestrictedFile(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> (restrictedFile: DebridIAFile?, newIA: DebridIA?) {
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
// Store the item in PM cloud for later use // Store the item in PM cloud for later use
try await createTransfer(magnet: magnet) try await createTransfer(magnet: magnet)
if let iaFile, let streamUrlString = iaFile.streamUrlString { if let iaFile {
return streamUrlString return (iaFile, nil)
} else if let premiumizeItem = ia, let firstFile = premiumizeItem.files[safe: 0], let streamUrlString = firstFile.streamUrlString { } else if let premiumizeItem = ia, let firstFile = premiumizeItem.files[safe: 0] {
return streamUrlString return (firstFile, nil)
} else { } else {
throw DebridError.FailedRequest(description: "Could not fetch your file from the Premiumize API") 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 { private func createTransfer(magnet: Magnet) async throws {
guard let magnetLink = magnet.link else { guard let magnetLink = magnet.link else {
throw DebridError.FailedRequest(description: "The magnet link is invalid") throw DebridError.FailedRequest(description: "The magnet link is invalid")

View file

@ -273,62 +273,50 @@ class RealDebrid: PollingDebridSource, ObservableObject {
continue continue
} }
// Is this a batch? // Handle files array
if data.rd.count > 1 || data.rd[0].count > 1 { let batches = data.rd.map { fileDict in
// Batch array let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in
let batches = data.rd.map { fileDict in // Force unwrapped ID. Is safe because ID is guaranteed on a successful response
let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in RealDebrid.IABatchFile(id: Int(key)!, fileName: value.filename)
// Force unwrapped ID. Is safe because ID is guaranteed on a successful response }.sorted(by: { $0.id < $1.id })
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 { for batch in batches {
let batchFileIds = batch.files.map(\.id) let batchFileIds = batch.files.map(\.id)
for batchFile in batch.files { for batchFile in batch.files {
if !files.contains(where: { $0.fileId == batchFile.id }) { if !files.contains(where: { $0.fileId == batchFile.id }) {
files.append( files.append(
DebridIAFile( DebridIAFile(
fileId: batchFile.id, fileId: batchFile.id,
name: batchFile.fileName, name: batchFile.fileName,
batchIds: batchFileIds 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 // MARK: - Downloading
// Wrapper function to fetch a download link from the API // 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 = "" var selectedMagnetId = ""
do { do {
@ -342,13 +330,12 @@ class RealDebrid: PollingDebridSource, ObservableObject {
} }
// RealDebrid has 1 as the first ID for a file // RealDebrid has 1 as the first ID for a file
let torrentLink = try await torrentInfo( let torrentFile = try await torrentInfo(
debridID: selectedMagnetId, debridID: selectedMagnetId,
selectedFileId: iaFile?.fileId ?? 1 selectedFileId: iaFile?.fileId ?? 1
) )
let downloadLink = try await unrestrictLink(debridDownloadLink: torrentLink)
return downloadLink return (torrentFile, nil)
} catch { } catch {
if case DebridError.EmptyTorrents = error, !selectedMagnetId.isEmpty { if case DebridError.EmptyTorrents = error, !selectedMagnetId.isEmpty {
try? await deleteTorrent(torrentId: selectedMagnetId) try? await deleteTorrent(torrentId: selectedMagnetId)
@ -401,7 +388,7 @@ class RealDebrid: PollingDebridSource, ObservableObject {
} }
// Gets the info of a torrent from a given ID // 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)")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!)
let data = try await performRequest(request: &request, requestName: #function) 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 // Let the user know if a torrent is downloading
if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" { 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" { } else if rawResponse.status == "downloading" || rawResponse.status == "queued" {
throw DebridError.IsCaching throw DebridError.IsCaching
} else { } else {
@ -420,13 +411,13 @@ class RealDebrid: PollingDebridSource, ObservableObject {
} }
// Downloads link from selectFiles for playback // 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")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/unrestrict/link")!)
request.httpMethod = "POST" request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
var bodyComponents = URLComponents() 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) request.httpBody = bodyComponents.query?.data(using: .utf8)

View file

@ -39,7 +39,10 @@ protocol DebridSource: AnyObservableObject {
// Fetches a download link from a source // Fetches a download link from a source
// Include the instant availability information with the args // Include the instant availability information with the args
// Torrents also checked here // 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 // User downloads functions
func getUserDownloads() async throws func getUserDownloads() async throws

View file

@ -39,6 +39,7 @@ class DebridManager: ObservableObject {
var selectedDebridItem: DebridIA? var selectedDebridItem: DebridIA?
var selectedDebridFile: DebridIAFile? var selectedDebridFile: DebridIAFile?
var requiresUnrestrict: Bool = false
// TODO: Figure out a way to remove this var // TODO: Figure out a way to remove this var
private var selectedOAuthDebridSource: OAuthDebridSource? private var selectedOAuthDebridSource: OAuthDebridSource?
@ -167,6 +168,11 @@ class DebridManager: ObservableObject {
if let IAItem = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash }) { if let IAItem = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
selectedDebridItem = IAItem selectedDebridItem = IAItem
if IAItem.files.count == 1 {
selectedDebridFile = IAItem.files[safe: 0]
}
return true return true
} else { } else {
logManager?.error("DebridManager: Could not find the associated \(selectedSource.id) entry for magnet hash \(magnetHash)") 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 // Cloudinfo is used for any extra information provided by debrid cloud
func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async { func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
defer { defer {
currentDebridTask = nil
logManager?.hideIndeterminateToast() logManager?.hideIndeterminateToast()
currentDebridTask = nil
} }
logManager?.updateIndeterminateToast("Loading content", cancelAction: { logManager?.updateIndeterminateToast("Loading content", cancelAction: {
@ -331,12 +337,24 @@ class DebridManager: ObservableObject {
} }
if let magnet { if let magnet {
let downloadLink = try await debridSource.getDownloadLink( let (restrictedFile, newIA) = try await debridSource.getRestrictedFile(
magnet: magnet, ia: selectedDebridItem, iaFile: selectedDebridFile 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 // Update the UI
downloadUrl = downloadLink downloadUrl = try await debridSource.unrestrictFile(restrictedFile)
} else { } else {
throw DebridError.FailedRequest(description: "Could not fetch your file from \(debridSource.id)'s cache or API") throw DebridError.FailedRequest(description: "Could not fetch your file from \(debridSource.id)'s cache or API")
} }
@ -350,8 +368,34 @@ class DebridManager: ObservableObject {
default: default:
await sendDebridError(error, prefix: "\(debridSource.id) download error", cancelString: "Download cancelled") await sendDebridError(error, prefix: "\(debridSource.id) download error", cancelString: "Download cancelled")
} }
}
return
}
func unrestrictDownload() async {
defer {
logManager?.hideIndeterminateToast() 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")
} }
} }

View file

@ -40,6 +40,14 @@ struct CloudTorrentView: View {
if cloudTorrent.links.count == 1 { if cloudTorrent.links.count == 1 {
await debridManager.fetchDebridDownload(magnet: magnet) await debridManager.fetchDebridDownload(magnet: magnet)
// Bump to batch
if debridManager.requiresUnrestrict {
navModel.selectedHistoryInfo = historyInfo
navModel.currentChoiceSheet = .batch
return
}
if !debridManager.downloadUrl.isEmpty { if !debridManager.downloadUrl.isEmpty {
historyInfo.url = debridManager.downloadUrl historyInfo.url = debridManager.downloadUrl
PersistenceController.shared.createHistory(historyInfo, performSave: true) PersistenceController.shared.createHistory(historyInfo, performSave: true)

View file

@ -32,17 +32,23 @@ struct SearchResultButtonView: View {
case .full: case .full:
if debridManager.selectDebridResult(magnet: result.magnet) { if debridManager.selectDebridResult(magnet: result.magnet) {
debridManager.currentDebridTask = Task { debridManager.currentDebridTask = Task {
let historyEntry = HistoryEntryJson(
name: result.title,
url: debridManager.downloadUrl,
source: result.source
)
await debridManager.fetchDebridDownload(magnet: result.magnet) await debridManager.fetchDebridDownload(magnet: result.magnet)
// Bump to batch
if debridManager.requiresUnrestrict {
navModel.selectedHistoryInfo = historyEntry
navModel.currentChoiceSheet = .batch
return
}
if !debridManager.downloadUrl.isEmpty { if !debridManager.downloadUrl.isEmpty {
PersistenceController.shared.createHistory( PersistenceController.shared.createHistory(historyEntry, performSave: true)
HistoryEntryJson(
name: result.title,
url: debridManager.downloadUrl,
source: result.source
),
performSave: true
)
pluginManager.runDefaultAction( pluginManager.runDefaultAction(
urlString: debridManager.downloadUrl, urlString: debridManager.downloadUrl,
@ -133,9 +139,9 @@ struct SearchResultButtonView: View {
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
Text( 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" + "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 .onReceive(NotificationCenter.default.publisher(for: .didDeleteBookmark)) { notification in

View file

@ -60,7 +60,11 @@ struct BatchChoiceView: View {
// Common function to communicate betwen VMs and queue/display a download // Common function to communicate betwen VMs and queue/display a download
func queueCommonDownload(fileName: String) { func queueCommonDownload(fileName: String) {
debridManager.currentDebridTask = Task { 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 { if !debridManager.downloadUrl.isEmpty {
try? await Task.sleep(seconds: 1) try? await Task.sleep(seconds: 1)