diff --git a/Ferrite/API/AllDebridWrapper.swift b/Ferrite/API/AllDebridWrapper.swift index 8a868c6..3f76226 100644 --- a/Ferrite/API/AllDebridWrapper.swift +++ b/Ferrite/API/AllDebridWrapper.swift @@ -125,7 +125,11 @@ public class AllDebrid { } // Adds a magnet link to the user's AD account - public func addMagnet(magnetLink: String) async throws -> Int { + public func addMagnet(magnet: Magnet) async throws -> Int { + guard let magnetLink = magnet.link else { + throw ADError.FailedRequest(description: "The magnet link is invalid") + } + var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/upload")) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") @@ -192,7 +196,7 @@ public class AllDebrid { } return IA( - hash: magnetResp.hash, + magnet: Magnet(hash: magnetResp.hash, link: magnetResp.magnet), expiryTimeStamp: Date().timeIntervalSince1970 + 300, files: files ) diff --git a/Ferrite/API/PremiumizeWrapper.swift b/Ferrite/API/PremiumizeWrapper.swift index 3c2f72f..ccd4207 100644 --- a/Ferrite/API/PremiumizeWrapper.swift +++ b/Ferrite/API/PremiumizeWrapper.swift @@ -148,6 +148,10 @@ public class Premiumize { // Grabs DDL links func fetchDDL(magnet: Magnet) async throws -> IA { + if magnet.hash == nil { + throw PMError.EmptyData + } + var request = URLRequest(url: URL(string: "\(baseApiUrl)/transfer/directdl")!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") @@ -169,7 +173,7 @@ public class Premiumize { } return IA( - hash: magnet.hash, + magnet: magnet, expiryTimeStamp: Date().timeIntervalSince1970 + 300, files: files ) @@ -178,7 +182,11 @@ public class Premiumize { } } - func createTransfer(magnetLink: String) async throws { + func createTransfer(magnet: Magnet) async throws { + guard let magnetLink = magnet.link else { + throw PMError.FailedRequest(description: "The magnet link is invalid") + } + var request = URLRequest(url: URL(string: "\(baseApiUrl)/transfer/create")!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 0f3f9bc..9b92a58 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -188,7 +188,7 @@ public class RealDebrid { // Currently does not work for batch links public func instantAvailability(magnets: [Magnet]) async throws -> [IA] { var availableHashes: [RealDebrid.IA] = [] - var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnets.map(\.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) @@ -241,7 +241,7 @@ public class RealDebrid { // TTL: 5 minutes availableHashes.append( RealDebrid.IA( - hash: hash, + magnet: Magnet(hash: hash, link: nil), expiryTimeStamp: Date().timeIntervalSince1970 + 300, files: files, batches: batches @@ -250,7 +250,7 @@ public class RealDebrid { } else { availableHashes.append( RealDebrid.IA( - hash: hash, + magnet: Magnet(hash: hash, link: nil), expiryTimeStamp: Date().timeIntervalSince1970 + 300 ) ) @@ -261,7 +261,11 @@ public class RealDebrid { } // Adds a magnet link to the user's RD account - public func addMagnet(magnetLink: String) async throws -> String { + public func addMagnet(magnet: Magnet) async throws -> String { + guard let magnetLink = magnet.link else { + throw RDError.FailedRequest(description: "The magnet link is invalid") + } + var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/addMagnet")!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") diff --git a/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift b/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift index dcd0f86..c12c191 100644 --- a/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift +++ b/Ferrite/DataManagement/Classes/Bookmark+CoreDataClass.swift @@ -16,8 +16,7 @@ public class Bookmark: NSManagedObject { title: title, source: source, size: size, - magnetLink: magnetLink, - magnetHash: magnetHash, + magnet: Magnet(hash: magnetHash, link: magnetLink), seeders: seeders, leechers: leechers ) diff --git a/Ferrite/Models/AllDebridModels.swift b/Ferrite/Models/AllDebridModels.swift index a253027..b37d130 100644 --- a/Ferrite/Models/AllDebridModels.swift +++ b/Ferrite/Models/AllDebridModels.swift @@ -145,7 +145,7 @@ public extension AllDebrid { // MARK: - InstantAvailablity client side structures struct IA: Codable, Hashable { - let hash: String + let magnet: Magnet let expiryTimeStamp: Double var files: [IAFile] } diff --git a/Ferrite/Models/BackupModels.swift b/Ferrite/Models/BackupModels.swift index 37b0f10..210eea8 100644 --- a/Ferrite/Models/BackupModels.swift +++ b/Ferrite/Models/BackupModels.swift @@ -8,6 +8,7 @@ import Foundation public struct Backup: Codable { + let version: Int var bookmarks: [BookmarkJson]? var history: [HistoryJson]? var sourceNames: [String]? @@ -16,7 +17,16 @@ public struct Backup: Codable { // MARK: - CoreData translation -typealias BookmarkJson = SearchResult +// Don't typealias to search result as this is a reflection of CoreData's struct +struct BookmarkJson: Codable { + let title: String? + let source: String + let size: String? + let magnetLink: String? + let magnetHash: String? + let seeders: String? + let leechers: String? +} // Date is an epoch timestamp struct HistoryJson: Codable { diff --git a/Ferrite/Models/DebridManagerModels.swift b/Ferrite/Models/DebridManagerModels.swift index 278d1c0..00cd554 100644 --- a/Ferrite/Models/DebridManagerModels.swift +++ b/Ferrite/Models/DebridManagerModels.swift @@ -6,6 +6,7 @@ // import Foundation +import Base32 // MARK: - Universal IA enum (IA = InstantAvailability) @@ -36,6 +37,63 @@ public enum DebridType: Int, Codable, Hashable, CaseIterable { // Wrapper struct for magnet links to contain both the link and hash for easy access public struct Magnet: Codable, Hashable, Sendable { - let link: String? - let hash: String + var hash: String? + var link: String? + + init(hash: String?, link: String?, title: String? = nil, trackers: [String]? = nil) { + if let hash = hash, link == nil { + self.hash = parseHash(hash) + self.link = generateLink(hash: hash, title: title, trackers: trackers) + } else if let link = link, hash == nil { + self.link = link + self.hash = parseHash(extractHash(link: link)) + } else { + self.hash = parseHash(hash) + self.link = link + } + } + + func generateLink(hash: String, title: String?, trackers: [String]?) -> String { + var magnetLinkArray = ["magnet:?xt=urn:btih:", hash] + + if let title, let encodedTitle = title.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) { + magnetLinkArray.append("&dn=\(encodedTitle)") + } + + if let trackers { + for trackerUrl in trackers { + if URL(string: trackerUrl) != nil, + let encodedUrlString = trackerUrl.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) + { + magnetLinkArray.append("&tr=\(encodedUrlString)") + } + } + } + + return magnetLinkArray.joined() + } + + func extractHash(link: String) -> String? { + if let firstSplit = link.split(separator: ":")[safe: 3], + let tempHash = firstSplit.split(separator: "&")[safe: 0] + { + return String(tempHash) + } else { + return nil + } + } + + // Is this a Base32hex hash? + func parseHash(_ magnetHash: String?) -> String? { + guard let magnetHash else { + return nil + } + + if magnetHash.count == 32 { + let decryptedMagnetHash = base32DecodeToData(String(magnetHash)) + return decryptedMagnetHash?.hexEncodedString() + } else { + return String(magnetHash).lowercased() + } + } } diff --git a/Ferrite/Models/PremiumizeModels.swift b/Ferrite/Models/PremiumizeModels.swift index 8beaa9c..08547e6 100644 --- a/Ferrite/Models/PremiumizeModels.swift +++ b/Ferrite/Models/PremiumizeModels.swift @@ -56,7 +56,7 @@ public extension Premiumize { // MARK: - InstantAvailability client side structures struct IA: Codable, Hashable { - let hash: String + let magnet: Magnet let expiryTimeStamp: Double let files: [IAFile] } diff --git a/Ferrite/Models/RealDebridModels.swift b/Ferrite/Models/RealDebridModels.swift index 2223959..393acb6 100644 --- a/Ferrite/Models/RealDebridModels.swift +++ b/Ferrite/Models/RealDebridModels.swift @@ -93,7 +93,7 @@ public extension RealDebrid { // MARK: - Instant Availability client side structures struct IA: Codable, Hashable, Sendable { - let hash: String + let magnet: Magnet let expiryTimeStamp: Double var files: [IAFile] = [] var batches: [IABatch] = [] diff --git a/Ferrite/Models/SearchModels.swift b/Ferrite/Models/SearchModels.swift index 8ebbe63..d09e0fa 100644 --- a/Ferrite/Models/SearchModels.swift +++ b/Ferrite/Models/SearchModels.swift @@ -11,8 +11,7 @@ public struct SearchResult: Codable, Hashable, Sendable { let title: String? let source: String let size: String? - let magnetLink: String? - let magnetHash: String? + let magnet: Magnet let seeders: String? let leechers: String? } diff --git a/Ferrite/ViewModels/BackupManager.swift b/Ferrite/ViewModels/BackupManager.swift index 5e7c516..bf7e534 100644 --- a/Ferrite/ViewModels/BackupManager.swift +++ b/Ferrite/ViewModels/BackupManager.swift @@ -8,6 +8,9 @@ import Foundation public class BackupManager: ObservableObject { + // Constant variable for backup versions + let latestBackupVersion: Int = 1 + var toastModel: ToastViewModel? @Published var showRestoreAlert = false @@ -18,7 +21,7 @@ public class BackupManager: ObservableObject { @Published var selectedBackupUrl: URL? func createBackup() { - var backup = Backup() + var backup = Backup(version: latestBackupVersion) let backgroundContext = PersistenceController.shared.backgroundContext let bookmarkRequest = Bookmark.fetchRequest() diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index 9ceef73..cea77c4 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -148,21 +148,21 @@ public class DebridManager: ObservableObject { // If a hash isn't found in the IA, update it // If the hash is expired, remove it and update it let sendMagnets = resultMagnets.filter { magnet in - if let IAIndex = realDebridIAValues.firstIndex(where: { $0.hash == magnet.hash }), enabledDebrids.contains(.realDebrid) { + if let IAIndex = realDebridIAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.realDebrid) { if now.timeIntervalSince1970 > realDebridIAValues[IAIndex].expiryTimeStamp { realDebridIAValues.remove(at: IAIndex) return true } else { return false } - } else if let IAIndex = allDebridIAValues.firstIndex(where: { $0.hash == magnet.hash }), enabledDebrids.contains(.allDebrid) { + } else if let IAIndex = allDebridIAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.allDebrid) { if now.timeIntervalSince1970 > allDebridIAValues[IAIndex].expiryTimeStamp { allDebridIAValues.remove(at: IAIndex) return true } else { return false } - } else if let IAIndex = premiumizeIAValues.firstIndex(where: { $0.hash == magnet.hash }), enabledDebrids.contains(.premiumize) { + } else if let IAIndex = premiumizeIAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.premiumize) { if now.timeIntervalSince1970 > premiumizeIAValues[IAIndex].expiryTimeStamp { premiumizeIAValues.remove(at: IAIndex) return true @@ -189,7 +189,7 @@ public class DebridManager: ObservableObject { // Only strip magnets that don't have an associated link for PM let strippedResultMagnets: [Magnet] = resultMagnets.compactMap { if let magnetLink = $0.link { - return Magnet(link: magnetLink, hash: $0.hash) + return Magnet(hash: $0.hash, link: magnetLink) } else { return nil } @@ -217,14 +217,14 @@ public class DebridManager: ObservableObject { } // Common function to match a magnet hash with a provided debrid service - public func matchMagnetHash(_ magnetHash: String?) -> IAStatus { - guard let magnetHash else { + public func matchMagnetHash(_ magnet: Magnet) -> IAStatus { + guard let magnetHash = magnet.hash else { return .none } switch selectedDebridType { case .realDebrid: - guard let realDebridMatch = realDebridIAValues.first(where: { magnetHash == $0.hash }) else { + guard let realDebridMatch = realDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) else { return .none } @@ -234,7 +234,7 @@ public class DebridManager: ObservableObject { return .partial } case .allDebrid: - guard let allDebridMatch = allDebridIAValues.first(where: { magnetHash == $0.hash }) else { + guard let allDebridMatch = allDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) else { return .none } @@ -244,7 +244,7 @@ public class DebridManager: ObservableObject { return .full } case .premiumize: - guard let premiumizeMatch = premiumizeIAValues.first(where: { magnetHash == $0.hash }) else { + guard let premiumizeMatch = premiumizeIAValues.first(where: { magnetHash == $0.magnet.hash }) else { return .none } @@ -258,15 +258,15 @@ public class DebridManager: ObservableObject { } } - public func selectDebridResult(magnetHash: String?) -> Bool { - guard let magnetHash = magnetHash else { + public func selectDebridResult(magnet: Magnet) -> Bool { + guard let magnetHash = magnet.hash else { toastModel?.updateToastDescription("Could not find the torrent magnet hash") return false } switch selectedDebridType { case .realDebrid: - if let realDebridItem = realDebridIAValues.first(where: { magnetHash == $0.hash }) { + if let realDebridItem = realDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) { selectedRealDebridItem = realDebridItem return true } else { @@ -274,7 +274,7 @@ public class DebridManager: ObservableObject { return false } case .allDebrid: - if let allDebridItem = allDebridIAValues.first(where: { magnetHash == $0.hash }) { + if let allDebridItem = allDebridIAValues.first(where: { magnetHash == $0.magnet.hash }) { selectedAllDebridItem = allDebridItem return true } else { @@ -282,7 +282,7 @@ public class DebridManager: ObservableObject { return false } case .premiumize: - if let premiumizeItem = premiumizeIAValues.first(where: { magnetHash == $0.hash }) { + if let premiumizeItem = premiumizeIAValues.first(where: { magnetHash == $0.magnet.hash }) { selectedPremiumizeItem = premiumizeItem return true } else { @@ -471,7 +471,7 @@ public class DebridManager: ObservableObject { // MARK: - Debrid fetch UI linked functions // Common function to delegate what debrid service to fetch from - public func fetchDebridDownload(magnetLink: String?) async { + public func fetchDebridDownload(magnet: Magnet?) async { defer { currentDebridTask = nil showLoadingProgress = false @@ -481,9 +481,9 @@ public class DebridManager: ObservableObject { switch selectedDebridType { case .realDebrid: - await fetchRdDownload(magnetLink: magnetLink) + await fetchRdDownload(magnet: magnet) case .allDebrid: - await fetchAdDownload(magnetLink: magnetLink) + await fetchAdDownload(magnet: magnet) case .premiumize: await fetchPmDownload() case .none: @@ -491,22 +491,22 @@ public class DebridManager: ObservableObject { } } - func fetchRdDownload(magnetLink: String?) async { + func fetchRdDownload(magnet: Magnet?) async { do { // Bypass the TTL since a download needs to be queried await fetchRdCloud(bypassTTL: true) // If there's an existing torrent, check for a download link. Otherwise check for an unrestrict link - let existingTorrents = realDebridCloudTorrents.filter { $0.hash == selectedRealDebridItem?.hash && $0.status == "downloaded" } + let existingTorrents = realDebridCloudTorrents.filter { $0.hash == selectedRealDebridItem?.magnet.hash && $0.status == "downloaded" } // If the links match from a user's downloads, no need to re-run a download if let existingTorrent = existingTorrents[safe: 0], let torrentLink = existingTorrent.links[safe: selectedRealDebridFile?.batchFileIndex ?? 0] { try await checkRdUserDownloads(userTorrentLink: torrentLink) - } else if let magnetLink = magnetLink { + } else if let magnet { // Add a magnet after all the cache checks fail - selectedRealDebridID = try await realDebrid.addMagnet(magnetLink: magnetLink) + selectedRealDebridID = try await realDebrid.addMagnet(magnet: magnet) var fileIds: [Int] = [] if let iaFile = selectedRealDebridFile { @@ -611,16 +611,16 @@ public class DebridManager: ObservableObject { } } - func fetchAdDownload(magnetLink: String?) async { - guard let magnetLink = magnetLink else { - toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.") - print("AllDebrid error: Invalid magnet link") + func fetchAdDownload(magnet: Magnet?) async { + guard let magnet else { + toastModel?.updateToastDescription("Could not run your action because the magnet is invalid.") + print("AllDebrid error: Invalid magnet") return } do { - let magnetID = try await allDebrid.addMagnet(magnetLink: magnetLink) + let magnetID = try await allDebrid.addMagnet(magnet: magnet) let lockedLink = try await allDebrid.fetchMagnetStatus( magnetId: magnetID, selectedIndex: selectedAllDebridFile?.id ?? 0 diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index 04d707c..66aae92 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -32,8 +32,7 @@ class NavigationViewModel: ObservableObject { @Published var isEditingSearch: Bool = false @Published var isSearching: Bool = false - @Published var selectedSearchResult: SearchResult? - @Published var selectedMagnetLink: String? + @Published var selectedMagnet: Magnet? @Published var selectedHistoryInfo: HistoryEntryJson? @Published var resultFromCloud: Bool = false @@ -96,16 +95,18 @@ class NavigationViewModel: ObservableObject { } } - public func runMagnetAction(magnetString: String?, _ action: DefaultMagnetActionType? = nil) { - let selectedAction = action ?? defaultMagnetAction - - guard let magnetLink = magnetString else { + public func runMagnetAction(magnet: Magnet?, _ action: DefaultMagnetActionType? = nil) { + // Fall back to selected magnet if the provided magnet is nil + let magnet = magnet ?? selectedMagnet + guard let magnetLink = magnet?.link else { toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.") print("Magnet action error: The magnet link is invalid.") return } + let selectedAction = action ?? defaultMagnetAction + switch selectedAction { case .none: currentChoiceSheet = .magnet @@ -126,24 +127,4 @@ class NavigationViewModel: ObservableObject { } } } - - /* - public func addToHistory(name: String?, source: String?, url: String?, subName: String? = nil) { - let backgroundContext = PersistenceController.shared.backgroundContext - - // The timeStamp and date are nil because the create function will make them automatically - PersistenceController.shared.createHistory( - entryJson: HistoryEntryJson( - name: name ?? "", - subName: subName, - url: url ?? "", - timeStamp: nil, - source: source - ), - date: nil - ) - - PersistenceController.shared.save(backgroundContext) - } - */ } diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index 92b8c75..e1fdab2 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -353,7 +353,7 @@ class ScrapingViewModel: ObservableObject { source: source, existingSearchResult: searchResult ), - let magnetLink = newSearchResult.magnetLink, + let magnetLink = newSearchResult.magnet.link, magnetLink.starts(with: "magnet:"), !tempResults.contains(newSearchResult) { @@ -362,7 +362,7 @@ class ScrapingViewModel: ObservableObject { } } else if let searchResult, - let magnetLink = searchResult.magnetLink, + let magnetLink = searchResult.magnet.link, magnetLink.starts(with: "magnet:"), !tempResults.contains(searchResult) { @@ -374,18 +374,16 @@ class ScrapingViewModel: ObservableObject { } public func parseJsonResult(_ result: JSON, jsonParser: SourceJsonParser, source: Source, existingSearchResult: SearchResult? = nil) -> SearchResult? { - var magnetHash: String? = existingSearchResult?.magnetHash - + var magnetHash: String? = existingSearchResult?.magnet.hash if let magnetHashParser = jsonParser.magnetHash { let rawHash = result[magnetHashParser.query.components(separatedBy: ".")].rawValue if !(rawHash is NSNull) { - magnetHash = fetchMagnetHash(existingHash: String(describing: rawHash)) + magnetHash = String(describing: rawHash) } } var title: String? = existingSearchResult?.title - if let titleParser = jsonParser.title { if let existingTitle = existingSearchResult?.title, let discriminatorQuery = titleParser.discriminator @@ -401,21 +399,13 @@ class ScrapingViewModel: ObservableObject { } } - var link: String? = existingSearchResult?.magnetLink - - if let magnetLinkParser = jsonParser.magnetLink, existingSearchResult?.magnetLink == nil { + var link: String? = existingSearchResult?.magnet.link + if let magnetLinkParser = jsonParser.magnetLink, link == nil { let rawLink = result[magnetLinkParser.query.components(separatedBy: ".")].rawValue link = rawLink is NSNull ? nil : String(describing: rawLink) - } else if let magnetHash { - link = generateMagnetLink(magnetHash: magnetHash, title: title, trackers: source.trackers) - } - - if magnetHash == nil, let href = link { - magnetHash = fetchMagnetHash(magnetLink: href) } var size: String? = existingSearchResult?.size - if let sizeParser = jsonParser.size, existingSearchResult?.size == nil { let rawSize = result[sizeParser.query.components(separatedBy: ".")].rawValue size = rawSize is NSNull ? nil : String(describing: rawSize) @@ -444,8 +434,7 @@ class ScrapingViewModel: ObservableObject { title: title, source: source.name, size: size, - magnetLink: link, - magnetHash: magnetHash, + magnet: Magnet(hash: magnetHash, link: link, title: title, trackers: source.trackers), seeders: seeders, leechers: leechers ) @@ -476,15 +465,13 @@ class ScrapingViewModel: ObservableObject { // Parse magnet link or translate hash var magnetHash: String? if let magnetHashParser = rssParser.magnetHash { - let tempHash = try? runRssComplexQuery( + magnetHash = try? runRssComplexQuery( item: item, query: magnetHashParser.query, attribute: magnetHashParser.attribute, discriminator: magnetHashParser.discriminator, regexString: magnetHashParser.regex ) - - magnetHash = fetchMagnetHash(existingHash: tempHash) } var title: String? @@ -507,8 +494,6 @@ class ScrapingViewModel: ObservableObject { discriminator: magnetLinkParser.discriminator, regexString: magnetLinkParser.regex ) - } else if let magnetHash { - link = generateMagnetLink(magnetHash: magnetHash, title: title, trackers: source.trackers) } else { continue } @@ -517,10 +502,6 @@ class ScrapingViewModel: ObservableObject { continue } - if magnetHash == nil { - magnetHash = fetchMagnetHash(magnetLink: href) - } - var size: String? if let sizeParser = rssParser.size { size = try? runRssComplexQuery( @@ -564,8 +545,7 @@ class ScrapingViewModel: ObservableObject { title: title ?? "No title", source: source.name, size: size ?? "", - magnetLink: href, - magnetHash: magnetHash, + magnet: Magnet(hash: magnetHash, link: href, title: title, trackers: source.trackers), seeders: seeders, leechers: leechers ) @@ -673,9 +653,6 @@ class ScrapingViewModel: ObservableObject { continue } - // Fetches the magnet hash - let magnetHash = fetchMagnetHash(magnetLink: href) - // Fetches the episode/movie title var title: String? if let titleParser = htmlParser.title { @@ -743,8 +720,7 @@ class ScrapingViewModel: ObservableObject { title: title ?? "No title", source: source.name, size: size ?? "", - magnetLink: href, - magnetHash: magnetHash, + magnet: Magnet(hash: nil, link: href), seeders: seeders, leechers: leechers ) @@ -786,31 +762,6 @@ class ScrapingViewModel: ObservableObject { } } - // Fetches and possibly converts the magnet hash value to sha1 - public func fetchMagnetHash(magnetLink: String? = nil, existingHash: String? = nil) -> String? { - var magnetHash: String - - if let existingHash { - magnetHash = existingHash - } else if - let magnetLink, - let firstSplit = magnetLink.split(separator: ":")[safe: 3], - let tempHash = firstSplit.split(separator: "&")[safe: 0] - { - magnetHash = String(tempHash) - } else { - return nil - } - - // Is this a Base32hex hash? - if magnetHash.count == 32 { - let decryptedMagnetHash = base32DecodeToData(String(magnetHash)) - return decryptedMagnetHash?.hexEncodedString() - } else { - return String(magnetHash).lowercased() - } - } - func parseSizeString(sizeString: String) -> String? { // Test if the string can be a full integer guard let size = Int(sizeString) else { @@ -833,28 +784,6 @@ class ScrapingViewModel: ObservableObject { } } - public func generateMagnetLink(magnetHash: String, title: String?, trackers: [String]?) -> String { - var magnetLinkArray = ["magnet:?xt=urn:btih:"] - - magnetLinkArray.append(magnetHash) - - if let title, let encodedTitle = title.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) { - magnetLinkArray.append("&dn=\(encodedTitle)") - } - - if let trackers { - for trackerUrl in trackers { - if URL(string: trackerUrl) != nil, - let encodedUrlString = trackerUrl.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) - { - magnetLinkArray.append("&tr=\(encodedUrlString)") - } - } - } - - return magnetLinkArray.joined() - } - func cleanApiCreds(api: SourceApi) async { let backgroundContext = PersistenceController.shared.backgroundContext diff --git a/Ferrite/Views/ComponentViews/Debrid/DebridLabelView.swift b/Ferrite/Views/ComponentViews/Debrid/DebridLabelView.swift index 21740b1..e6134d7 100644 --- a/Ferrite/Views/ComponentViews/Debrid/DebridLabelView.swift +++ b/Ferrite/Views/ComponentViews/Debrid/DebridLabelView.swift @@ -11,7 +11,7 @@ struct DebridLabelView: View { @EnvironmentObject var debridManager: DebridManager @State var cloudLinks: [String] = [] - var magnetHash: String? + var magnet: Magnet? var body: some View { if let selectedDebridType = debridManager.selectedDebridType { @@ -20,8 +20,8 @@ struct DebridLabelView: View { .padding(2) .background { Group { - if cloudLinks.isEmpty { - switch debridManager.matchMagnetHash(magnetHash) { + if let magnet, cloudLinks.isEmpty { + switch debridManager.matchMagnetHash(magnet) { case .full: Color.green case .partial: diff --git a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift index 9167a78..ef8743e 100644 --- a/Ferrite/Views/ComponentViews/Library/BookmarksView.swift +++ b/Ferrite/Views/ComponentViews/Library/BookmarksView.swift @@ -56,7 +56,7 @@ struct BookmarksView: View { viewTask = Task { let magnets = bookmarks.compactMap { if let magnetHash = $0.magnetHash { - return Magnet(link: $0.magnetLink, hash: magnetHash) + return Magnet(hash: magnetHash, link: $0.magnetLink) } else { return nil } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift index 1dd1413..fb365e6 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift @@ -20,12 +20,12 @@ struct RealDebridCloudView: View { Button(downloadResponse.filename) { navModel.resultFromCloud = true navModel.selectedTitle = downloadResponse.filename - debridManager.downloadUrl = downloadResponse.link + debridManager.downloadUrl = downloadResponse.download PersistenceController.shared.createHistory( HistoryEntryJson( name: downloadResponse.filename, - url: downloadResponse.link, + url: downloadResponse.download, source: DebridType.realDebrid.toString() ) ) @@ -73,9 +73,10 @@ struct RealDebridCloudView: View { } } else { debridManager.clearIAValues() - await debridManager.populateDebridIA([Magnet(link: nil, hash: torrentResponse.hash)]) + let magnet = Magnet(hash: torrentResponse.hash, link: nil) + await debridManager.populateDebridIA([magnet]) - if debridManager.selectDebridResult(magnetHash: torrentResponse.hash) { + if debridManager.selectDebridResult(magnet: magnet) { navModel.selectedHistoryInfo = historyInfo navModel.currentChoiceSheet = .batch } diff --git a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift index 777b0ed..3a52d2d 100644 --- a/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift +++ b/Ferrite/Views/ComponentViews/Library/HistoryButtonView.swift @@ -30,7 +30,7 @@ struct HistoryButtonView: View { } } } else { - navModel.runMagnetAction(magnetString: url) + navModel.runMagnetAction(magnet: Magnet(hash: nil, link: url)) } } else { toastModel.updateToastDescription("URL invalid. Cannot load this history entry. Please delete it.") diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift index ce424b3..cec3138 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultButtonView.swift @@ -22,15 +22,15 @@ struct SearchResultButtonView: View { var body: some View { Button { if debridManager.currentDebridTask == nil { - navModel.selectedSearchResult = result + navModel.selectedMagnet = result.magnet navModel.selectedTitle = result.title ?? "" navModel.resultFromCloud = false - switch debridManager.matchMagnetHash(result.magnetHash) { + switch debridManager.matchMagnetHash(result.magnet) { case .full: - if debridManager.selectDebridResult(magnetHash: result.magnetHash) { + if debridManager.selectDebridResult(magnet: result.magnet) { debridManager.currentDebridTask = Task { - await debridManager.fetchDebridDownload(magnetLink: result.magnetLink) + await debridManager.fetchDebridDownload(magnet: result.magnet) if !debridManager.downloadUrl.isEmpty { PersistenceController.shared.createHistory( @@ -50,19 +50,19 @@ struct SearchResultButtonView: View { } } case .partial: - if debridManager.selectDebridResult(magnetHash: result.magnetHash) { + if debridManager.selectDebridResult(magnet: result.magnet) { navModel.currentChoiceSheet = .batch } case .none: PersistenceController.shared.createHistory( HistoryEntryJson( name: result.title, - url: result.magnetLink, + url: result.magnet.link, source: result.source ) ) - navModel.runMagnetAction(magnetString: result.magnetLink) + navModel.runMagnetAction(magnet: result.magnet) } } } label: { @@ -95,8 +95,8 @@ struct SearchResultButtonView: View { let newBookmark = Bookmark(context: backgroundContext) newBookmark.title = result.title newBookmark.source = result.source - newBookmark.magnetHash = result.magnetHash - newBookmark.magnetLink = result.magnetLink + newBookmark.magnetHash = result.magnet.hash + newBookmark.magnetLink = result.magnet.link newBookmark.seeders = result.seeders newBookmark.leechers = result.leechers @@ -139,8 +139,8 @@ struct SearchResultButtonView: View { format: "title == %@ AND source == %@ AND magnetLink == %@ AND magnetHash = %@", result.title ?? "", result.source, - result.magnetLink ?? "", - result.magnetHash ?? "" + result.magnet.link ?? "", + result.magnet.hash ?? "" ) bookmarkRequest.fetchLimit = 1 diff --git a/Ferrite/Views/ComponentViews/SearchResult/SearchResultInfoView.swift b/Ferrite/Views/ComponentViews/SearchResult/SearchResultInfoView.swift index 86d1751..d42371d 100644 --- a/Ferrite/Views/ComponentViews/SearchResult/SearchResultInfoView.swift +++ b/Ferrite/Views/ComponentViews/SearchResult/SearchResultInfoView.swift @@ -30,7 +30,7 @@ struct SearchResultInfoView: View { Text(size) } - DebridLabelView(magnetHash: result.magnetHash) + DebridLabelView(magnet: result.magnet) } .font(.caption) } diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 10c15d0..b691ba5 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -91,8 +91,8 @@ struct ContentView: View { // Remove magnets that don't have a hash let magnets = scrapingModel.searchResults.compactMap { - if let magnetHash = $0.magnetHash { - return Magnet(link: $0.magnetLink, hash: magnetHash) + if let magnetHash = $0.magnet.hash { + return Magnet(hash: magnetHash, link: $0.magnet.link) } else { return nil } diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index 90ecd01..fa1f094 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -70,7 +70,7 @@ struct BatchChoiceView: View { // Common function to communicate betwen VMs and queue/display a download func queueCommonDownload(fileName: String) { debridManager.currentDebridTask = Task { - await debridManager.fetchDebridDownload(magnetLink: navModel.resultFromCloud ? nil : navModel.selectedMagnetLink) + await debridManager.fetchDebridDownload(magnet: navModel.resultFromCloud ? nil : navModel.selectedMagnet) if !debridManager.downloadUrl.isEmpty { try? await Task.sleep(seconds: 1) diff --git a/Ferrite/Views/SheetViews/MagnetChoiceView.swift b/Ferrite/Views/SheetViews/MagnetChoiceView.swift index aaeaf31..1690ddb 100644 --- a/Ferrite/Views/SheetViews/MagnetChoiceView.swift +++ b/Ferrite/Views/SheetViews/MagnetChoiceView.swift @@ -74,7 +74,7 @@ struct MagnetChoiceView: View { if !navModel.resultFromCloud { Section(header: "Magnet options") { ListRowButtonView("Copy magnet", systemImage: "doc.on.doc.fill") { - UIPasteboard.general.string = navModel.selectedMagnetLink + UIPasteboard.general.string = navModel.selectedMagnet?.link showMagnetCopyAlert.toggle() } .backport.alert( @@ -85,7 +85,7 @@ struct MagnetChoiceView: View { ) ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") { - if let magnetLink = navModel.selectedMagnetLink, + if let magnetLink = navModel.selectedMagnet?.link, let url = URL(string: magnetLink) { navModel.activityItems = [url] @@ -94,7 +94,7 @@ struct MagnetChoiceView: View { } ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") { - navModel.runMagnetAction(magnetString: navModel.selectedMagnetLink, .webtor) + navModel.runMagnetAction(magnet: navModel.selectedMagnet, .webtor) } } }