mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
Library: Add support for Premiumize cloud
Add the ability to view a user's Premiumize files in Ferrite. Files can be deleted from a user's account directly in Ferrite's list. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
9b7bc55a25
commit
9f54397b77
8 changed files with 258 additions and 47 deletions
|
|
@ -100,6 +100,7 @@
|
|||
0CA3FB2028B91D9500FA10A8 /* IndeterminateProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */; };
|
||||
0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA429F728C5098D000D0610 /* DateFormatter.swift */; };
|
||||
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; };
|
||||
0CAF9319296399190050812A /* PremiumizeCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CAF9318296399190050812A /* PremiumizeCloudView.swift */; };
|
||||
0CB6516328C5A57300DCA721 /* ConditionalId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516228C5A57300DCA721 /* ConditionalId.swift */; };
|
||||
0CB6516528C5A5D700DCA721 /* InlinedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB6516428C5A5D700DCA721 /* InlinedList.swift */; };
|
||||
0CB6516828C5A5EC00DCA721 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 0CB6516728C5A5EC00DCA721 /* Introspect */; };
|
||||
|
|
@ -205,6 +206,7 @@
|
|||
0CA3FB1F28B91D9500FA10A8 /* IndeterminateProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressView.swift; sourceTree = "<group>"; };
|
||||
0CA429F728C5098D000D0610 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = "<group>"; };
|
||||
0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0CAF9318296399190050812A /* PremiumizeCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumizeCloudView.swift; sourceTree = "<group>"; };
|
||||
0CB6516228C5A57300DCA721 /* ConditionalId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalId.swift; sourceTree = "<group>"; };
|
||||
0CB6516428C5A5D700DCA721 /* InlinedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinedList.swift; sourceTree = "<group>"; };
|
||||
0CB6516928C5B4A600DCA721 /* InlineHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHeader.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -314,6 +316,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */,
|
||||
0CAF9318296399190050812A /* PremiumizeCloudView.swift */,
|
||||
);
|
||||
path = Cloud;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -679,6 +682,7 @@
|
|||
0CD72E17293D9928001A7EA4 /* Array.swift in Sources */,
|
||||
0C44E2A828D4DDDC007711AE /* Application.swift in Sources */,
|
||||
0C7ED14128D61BBA009E29AD /* BackupModels.swift in Sources */,
|
||||
0CAF9319296399190050812A /* PremiumizeCloudView.swift in Sources */,
|
||||
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
|
||||
0C95D8D828A55B03005E22B3 /* DefaultActionsPickerViews.swift in Sources */,
|
||||
0C44E2AF28D52E8A007711AE /* BackupsView.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public class Premiumize {
|
|||
return data
|
||||
} else if response.statusCode == 401 {
|
||||
deleteTokens()
|
||||
throw PMError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to AllDebrid in Settings.")
|
||||
throw PMError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to Premiumize in Settings.")
|
||||
} else {
|
||||
throw PMError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||
}
|
||||
|
|
@ -177,4 +177,58 @@ public class Premiumize {
|
|||
throw PMError.EmptyData
|
||||
}
|
||||
}
|
||||
|
||||
func createTransfer(magnetLink: String) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/transfer/create")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
var bodyComponents = URLComponents()
|
||||
bodyComponents.queryItems = [URLQueryItem(name: "src", value: magnetLink)]
|
||||
|
||||
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
||||
|
||||
try await performRequest(request: &request, requestName: #function)
|
||||
}
|
||||
|
||||
func userItems() async throws -> [UserItem] {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/listall")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
let rawResponse = try jsonDecoder.decode(AllItemsResponse.self, from: data)
|
||||
|
||||
if rawResponse.files.isEmpty {
|
||||
throw PMError.EmptyData
|
||||
}
|
||||
|
||||
return rawResponse.files
|
||||
}
|
||||
|
||||
func itemDetails(itemID: String) async throws -> ItemDetailsResponse {
|
||||
var urlComponents = URLComponents(string: "\(baseApiUrl)/item/details")!
|
||||
urlComponents.queryItems = [URLQueryItem(name: "id", value: itemID)]
|
||||
guard let url = urlComponents.url else {
|
||||
throw PMError.InvalidUrl
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
let rawResponse = try jsonDecoder.decode(ItemDetailsResponse.self, from: data)
|
||||
|
||||
return rawResponse
|
||||
}
|
||||
|
||||
func deleteItem(itemID: String) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/delete")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
var bodyComponents = URLComponents()
|
||||
bodyComponents.queryItems = [URLQueryItem(name: "id", value: itemID)]
|
||||
|
||||
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
||||
|
||||
try await performRequest(request: &request, requestName: #function)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public extension Premiumize {
|
|||
let filesize: Int
|
||||
}
|
||||
|
||||
// MARK: - Content
|
||||
// MARK: Content
|
||||
|
||||
struct DDLData: Codable {
|
||||
let path: String
|
||||
|
|
@ -65,4 +65,38 @@ public extension Premiumize {
|
|||
let name: String
|
||||
let streamUrlString: String
|
||||
}
|
||||
|
||||
// MARK: - AllItemsResponse (listall endpoint)
|
||||
struct AllItemsResponse: Codable {
|
||||
let status: String
|
||||
let files: [UserItem]
|
||||
}
|
||||
|
||||
// MARK: User Items
|
||||
// Abridged for required parameters
|
||||
struct UserItem: Codable {
|
||||
let id: String
|
||||
let name: String
|
||||
let mimeType: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name
|
||||
case mimeType = "mime_type"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ItemDetailsResponse
|
||||
|
||||
// Abridged for required parameters
|
||||
struct ItemDetailsResponse: Codable {
|
||||
let id: String
|
||||
let name: String
|
||||
let link: String
|
||||
let mimeType: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, link
|
||||
case mimeType = "mime_type"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ public class DebridManager: ObservableObject {
|
|||
var selectedPremiumizeItem: Premiumize.IA?
|
||||
var selectedPremiumizeFile: Premiumize.IAFile?
|
||||
|
||||
// Premiumize cloud variables
|
||||
@Published var premiumizeCloudItems: [Premiumize.UserItem] = []
|
||||
var premiumizeCloudTTL: Double = 0.0
|
||||
|
||||
init() {
|
||||
if let rawDebridList = UserDefaults.standard.string(forKey: "Debrid.EnabledArray"),
|
||||
let serializedDebridList = Set<DebridType>(rawValue: rawDebridList)
|
||||
|
|
@ -481,7 +485,7 @@ public class DebridManager: ObservableObject {
|
|||
case .allDebrid:
|
||||
await fetchAdDownload(magnetLink: magnetLink)
|
||||
case .premiumize:
|
||||
fetchPmDownload()
|
||||
await fetchPmDownload()
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
|
@ -544,7 +548,7 @@ public class DebridManager: ObservableObject {
|
|||
toastModel?.updateToastDescription("RealDebrid download error: \(error)")
|
||||
}
|
||||
|
||||
await deleteRdTorrent()
|
||||
await deleteRdTorrent(torrentID: selectedRealDebridID)
|
||||
}
|
||||
|
||||
showLoadingProgress = false
|
||||
|
|
@ -564,16 +568,36 @@ public class DebridManager: ObservableObject {
|
|||
realDebridCloudTTL = Date().timeIntervalSince1970 + 300
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("RealDebrid cloud fetch error: \(error)")
|
||||
print("RealDebrid cloud fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRdTorrent() async {
|
||||
if let realDebridId = selectedRealDebridID {
|
||||
try? await realDebrid.deleteTorrent(debridID: realDebridId)
|
||||
}
|
||||
func deleteRdDownload(downloadID: String) async {
|
||||
do {
|
||||
try await realDebrid.deleteDownload(debridID: downloadID)
|
||||
|
||||
selectedRealDebridID = nil
|
||||
// Bypass TTL to get current RD values
|
||||
await fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("RealDebrid download delete error: \(error)")
|
||||
print("RealDebrid download delete error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRdTorrent(torrentID: String? = nil) async {
|
||||
do {
|
||||
if let torrentID = torrentID {
|
||||
try await realDebrid.deleteTorrent(debridID: torrentID)
|
||||
} else if let selectedTorrentID = selectedRealDebridID {
|
||||
try await realDebrid.deleteTorrent(debridID: selectedTorrentID)
|
||||
} else {
|
||||
throw RealDebrid.RDError.FailedRequest(description: "No torrent ID was provided")
|
||||
}
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("RealDebrid torrent delete error: \(error)")
|
||||
print("RealDebrid torrent delete error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func checkRdUserDownloads(userTorrentLink: String) async throws {
|
||||
|
|
@ -615,21 +639,53 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func fetchPmDownload() {
|
||||
guard let premiumizeItem = selectedPremiumizeItem else {
|
||||
toastModel?.updateToastDescription("Could not run your action because the result is invalid")
|
||||
print("Premiumize download error: Invalid selected Premiumize item")
|
||||
|
||||
return
|
||||
func fetchPmDownload(cloudItemId: String? = nil) async {
|
||||
do {
|
||||
if let cloudItemId = cloudItemId {
|
||||
downloadUrl = try await premiumize.itemDetails(itemID: cloudItemId).link
|
||||
} else if let premiumizeFile = selectedPremiumizeFile {
|
||||
downloadUrl = premiumizeFile.streamUrlString
|
||||
} else if
|
||||
let premiumizeItem = selectedPremiumizeItem,
|
||||
let firstFile = premiumizeItem.files[safe: 0]
|
||||
{
|
||||
downloadUrl = firstFile.streamUrlString
|
||||
} else {
|
||||
throw Premiumize.PMError.FailedRequest(description: "There were no items or files found!")
|
||||
}
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("Premiumize download error: \(error)")
|
||||
print("Premiumize download error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if let premiumizeFile = selectedPremiumizeFile {
|
||||
downloadUrl = premiumizeFile.streamUrlString
|
||||
} else if let firstFile = premiumizeItem.files[safe: 0] {
|
||||
downloadUrl = firstFile.streamUrlString
|
||||
} else {
|
||||
toastModel?.updateToastDescription("Could not run your action because the result could not be found")
|
||||
print("Premiumize download error: Could not find the selected Premiumize file")
|
||||
// Refreshes items and fetches from a PM user account
|
||||
public func fetchPmCloud(bypassTTL: Bool = false) async {
|
||||
if bypassTTL || Date().timeIntervalSince1970 > premiumizeCloudTTL {
|
||||
do {
|
||||
let userItems = try await premiumize.userItems()
|
||||
withAnimation {
|
||||
premiumizeCloudItems = userItems
|
||||
}
|
||||
|
||||
// 5 minutes
|
||||
premiumizeCloudTTL = Date().timeIntervalSince1970 + 300
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("Premiumize cloud fetch error: \(error)")
|
||||
print("Premiumize cloud fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func deletePmItem(id: String) async {
|
||||
do {
|
||||
try await premiumize.deleteItem(itemID: id)
|
||||
|
||||
// Bypass TTL to get current RD values
|
||||
await fetchPmCloud(bypassTTL: true)
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("Premiumize cloud delete error: \(error)")
|
||||
print("Premiumize cloud delete error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// PremiumizeCloudView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 1/2/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftUIX
|
||||
|
||||
struct PremiumizeCloudView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
|
||||
@State private var viewTask: Task<Void, Never>?
|
||||
|
||||
@State private var searchText: String = ""
|
||||
|
||||
var body: some View {
|
||||
DisclosureGroup("Items") {
|
||||
ForEach(debridManager.premiumizeCloudItems, id: \.id) { item in
|
||||
Button(item.name) {
|
||||
Task {
|
||||
navModel.resultFromCloud = true
|
||||
navModel.selectedTitle = item.name
|
||||
|
||||
await debridManager.fetchPmDownload(cloudItemId: item.id)
|
||||
|
||||
if !debridManager.downloadUrl.isEmpty {
|
||||
PersistenceController.shared.createHistory(
|
||||
HistoryEntryJson(
|
||||
name: item.name,
|
||||
url: debridManager.downloadUrl,
|
||||
source: "Premiumize"
|
||||
)
|
||||
)
|
||||
|
||||
navModel.runDebridAction(urlString: debridManager.downloadUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2))
|
||||
.backport.tint(.black)
|
||||
}
|
||||
.onDelete { offsets in
|
||||
for index in offsets {
|
||||
if let item = debridManager.premiumizeCloudItems[safe: index] {
|
||||
Task {
|
||||
await debridManager.deletePmItem(id: item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewTask = Task {
|
||||
await debridManager.fetchPmCloud()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
viewTask?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PremiumizeCloudView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PremiumizeCloudView()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,14 +38,7 @@ struct RealDebridCloudView: View {
|
|||
for index in offsets {
|
||||
if let downloadResponse = debridManager.realDebridCloudDownloads[safe: index] {
|
||||
Task {
|
||||
do {
|
||||
try await debridManager.realDebrid.deleteDownload(debridID: downloadResponse.id)
|
||||
|
||||
// Bypass TTL to get current RD values
|
||||
await debridManager.fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
await debridManager.deleteRdDownload(downloadID: downloadResponse.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,14 +104,7 @@ struct RealDebridCloudView: View {
|
|||
for index in offsets {
|
||||
if let torrentResponse = debridManager.realDebridCloudTorrents[safe: index] {
|
||||
Task {
|
||||
do {
|
||||
try await debridManager.realDebrid.deleteTorrent(debridID: torrentResponse.id)
|
||||
|
||||
// Bypass TTL to get current RD values
|
||||
await debridManager.fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
await debridManager.deleteRdTorrent(torrentID: torrentResponse.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,21 +6,28 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftUIX
|
||||
|
||||
struct DebridCloudView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
switch debridManager.selectedDebridType {
|
||||
case .realDebrid:
|
||||
RealDebridCloudView()
|
||||
case .allDebrid, .premiumize, .none:
|
||||
EmptyView()
|
||||
NavView {
|
||||
VStack {
|
||||
List {
|
||||
switch debridManager.selectedDebridType {
|
||||
case .realDebrid:
|
||||
RealDebridCloudView()
|
||||
case .premiumize:
|
||||
PremiumizeCloudView()
|
||||
case .allDebrid, .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.inlinedList()
|
||||
.listStyle(.grouped)
|
||||
}
|
||||
}
|
||||
.inlinedList()
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ struct LibraryView: View {
|
|||
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
||||
}
|
||||
case .debridCloud:
|
||||
if debridManager.selectedDebridType != .realDebrid {
|
||||
if debridManager.selectedDebridType == nil || debridManager.selectedDebridType == .allDebrid {
|
||||
EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue