RealDebrid, Github: Reorganize models
Prep for more debrid services Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
a1cd62d3b9
commit
06d4f8e84e
7 changed files with 230 additions and 217 deletions
8
Ferrite/API/AllDebridWrapper.swift
Normal file
8
Ferrite/API/AllDebridWrapper.swift
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
//
|
||||||
|
// AllDebridWrapper.swift
|
||||||
|
// Ferrite
|
||||||
|
//
|
||||||
|
// Created by Brian Dashore on 11/25/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
@ -8,21 +8,21 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class Github {
|
public class Github {
|
||||||
public func fetchLatestRelease() async throws -> GithubRelease? {
|
public func fetchLatestRelease() async throws -> Release? {
|
||||||
let url = URL(string: "https://api.github.com/repos/bdashore3/Ferrite/releases/latest")!
|
let url = URL(string: "https://api.github.com/repos/bdashore3/Ferrite/releases/latest")!
|
||||||
|
|
||||||
let (data, _) = try await URLSession.shared.data(from: url)
|
let (data, _) = try await URLSession.shared.data(from: url)
|
||||||
|
|
||||||
let rawResponse = try JSONDecoder().decode(GithubRelease.self, from: data)
|
let rawResponse = try JSONDecoder().decode(Release.self, from: data)
|
||||||
return rawResponse
|
return rawResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchReleases() async throws -> [GithubRelease]? {
|
public func fetchReleases() async throws -> [Release]? {
|
||||||
let url = URL(string: "https://api.github.com/repos/bdashore3/Ferrite/releases")!
|
let url = URL(string: "https://api.github.com/repos/bdashore3/Ferrite/releases")!
|
||||||
|
|
||||||
let (data, _) = try await URLSession.shared.data(from: url)
|
let (data, _) = try await URLSession.shared.data(from: url)
|
||||||
|
|
||||||
let rawResponse = try JSONDecoder().decode([GithubRelease].self, from: data)
|
let rawResponse = try JSONDecoder().decode([Release].self, from: data)
|
||||||
return rawResponse
|
return rawResponse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import KeychainSwift
|
import KeychainSwift
|
||||||
|
|
||||||
public enum RealDebridError: Error {
|
|
||||||
case InvalidUrl
|
|
||||||
case InvalidPostBody
|
|
||||||
case InvalidResponse
|
|
||||||
case InvalidToken
|
|
||||||
case EmptyData
|
|
||||||
case EmptyTorrents
|
|
||||||
case FailedRequest(description: String)
|
|
||||||
case AuthQuery(description: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RealDebrid {
|
public class RealDebrid {
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
let keychain = KeychainSwift()
|
let keychain = KeychainSwift()
|
||||||
|
|
@ -38,7 +27,7 @@ public class RealDebrid {
|
||||||
]
|
]
|
||||||
|
|
||||||
guard let url = urlComponents.url else {
|
guard let url = urlComponents.url else {
|
||||||
throw RealDebridError.InvalidUrl
|
throw RDError.InvalidUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = URLRequest(url: url)
|
let request = URLRequest(url: url)
|
||||||
|
|
@ -49,7 +38,7 @@ public class RealDebrid {
|
||||||
return rawResponse
|
return rawResponse
|
||||||
} catch {
|
} catch {
|
||||||
print("Couldn't get the new client creds!")
|
print("Couldn't get the new client creds!")
|
||||||
throw RealDebridError.AuthQuery(description: error.localizedDescription)
|
throw RDError.AuthQuery(description: error.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +51,7 @@ public class RealDebrid {
|
||||||
]
|
]
|
||||||
|
|
||||||
guard let url = urlComponents.url else {
|
guard let url = urlComponents.url else {
|
||||||
throw RealDebridError.InvalidUrl
|
throw RDError.InvalidUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = URLRequest(url: url)
|
let request = URLRequest(url: url)
|
||||||
|
|
@ -73,7 +62,7 @@ public class RealDebrid {
|
||||||
|
|
||||||
while count < 20 {
|
while count < 20 {
|
||||||
if Task.isCancelled {
|
if Task.isCancelled {
|
||||||
throw RealDebridError.AuthQuery(description: "Token request cancelled.")
|
throw RDError.AuthQuery(description: "Token request cancelled.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
let (data, _) = try await URLSession.shared.data(for: request)
|
||||||
|
|
@ -95,7 +84,7 @@ public class RealDebrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw RealDebridError.AuthQuery(description: "Could not fetch the client ID and secret in time. Try logging in again.")
|
throw RDError.AuthQuery(description: "Could not fetch the client ID and secret in time. Try logging in again.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .failure(error) = await authTask?.result {
|
if case let .failure(error) = await authTask?.result {
|
||||||
|
|
@ -106,11 +95,11 @@ public class RealDebrid {
|
||||||
// Fetch all tokens for the user and store in keychain
|
// Fetch all tokens for the user and store in keychain
|
||||||
public func getTokens(deviceCode: String) async throws {
|
public func getTokens(deviceCode: String) async throws {
|
||||||
guard let clientId = UserDefaults.standard.string(forKey: "RealDebrid.ClientId") else {
|
guard let clientId = UserDefaults.standard.string(forKey: "RealDebrid.ClientId") else {
|
||||||
throw RealDebridError.EmptyData
|
throw RDError.EmptyData
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let clientSecret = keychain.get("RealDebrid.ClientSecret") else {
|
guard let clientSecret = keychain.get("RealDebrid.ClientSecret") else {
|
||||||
throw RealDebridError.EmptyData
|
throw RDError.EmptyData
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = URLRequest(url: URL(string: "\(baseAuthUrl)/token")!)
|
var request = URLRequest(url: URL(string: "\(baseAuthUrl)/token")!)
|
||||||
|
|
@ -174,7 +163,7 @@ public class RealDebrid {
|
||||||
// Wrapper request function which matches the responses and returns data
|
// Wrapper request function which matches the responses and returns data
|
||||||
@discardableResult public func performRequest(request: inout URLRequest, requestName: String) async throws -> Data {
|
@discardableResult public func performRequest(request: inout URLRequest, requestName: String) async throws -> Data {
|
||||||
guard let token = await fetchToken() else {
|
guard let token = await fetchToken() else {
|
||||||
throw RealDebridError.InvalidToken
|
throw RDError.InvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
|
@ -182,23 +171,23 @@ public class RealDebrid {
|
||||||
let (data, response) = try await URLSession.shared.data(for: request)
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
guard let response = response as? HTTPURLResponse else {
|
guard let response = response as? HTTPURLResponse else {
|
||||||
throw RealDebridError.FailedRequest(description: "No HTTP response given")
|
throw RDError.FailedRequest(description: "No HTTP response given")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.statusCode >= 200, response.statusCode <= 299 {
|
if response.statusCode >= 200, response.statusCode <= 299 {
|
||||||
return data
|
return data
|
||||||
} else if response.statusCode == 401 {
|
} else if response.statusCode == 401 {
|
||||||
try await deleteTokens()
|
try await deleteTokens()
|
||||||
throw RealDebridError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to RealDebrid in Settings.")
|
throw RDError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to RealDebrid in Settings.")
|
||||||
} else {
|
} else {
|
||||||
throw RealDebridError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
throw RDError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the magnet is streamable on RD
|
// Checks if the magnet is streamable on RD
|
||||||
// Currently does not work for batch links
|
// Currently does not work for batch links
|
||||||
public func instantAvailability(magnetHashes: [String]) async throws -> [RealDebridIA] {
|
public func instantAvailability(magnetHashes: [String]) async throws -> [RealDebrid.IA] {
|
||||||
var availableHashes: [RealDebridIA] = []
|
var availableHashes: [RealDebrid.IA] = []
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnetHashes.joined(separator: "/"))")!)
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnetHashes.joined(separator: "/"))")!)
|
||||||
|
|
||||||
let data = try await performRequest(request: &request, requestName: #function)
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
|
|
@ -219,17 +208,17 @@ public class RealDebrid {
|
||||||
if data.rd.count > 1 || data.rd[0].count > 1 {
|
if data.rd.count > 1 || data.rd[0].count > 1 {
|
||||||
// Batch array
|
// Batch array
|
||||||
let batches = data.rd.map { fileDict in
|
let batches = data.rd.map { fileDict in
|
||||||
let batchFiles: [RealDebridIABatchFile] = fileDict.map { key, value in
|
let batchFiles: [RealDebrid.IABatchFile] = fileDict.map { key, value in
|
||||||
// Force unwrapped ID. Is safe because ID is guaranteed on a successful response
|
// Force unwrapped ID. Is safe because ID is guaranteed on a successful response
|
||||||
RealDebridIABatchFile(id: Int(key)!, fileName: value.filename)
|
RealDebrid.IABatchFile(id: Int(key)!, fileName: value.filename)
|
||||||
}.sorted(by: { $0.id < $1.id })
|
}.sorted(by: { $0.id < $1.id })
|
||||||
|
|
||||||
return RealDebridIABatch(files: batchFiles)
|
return RealDebrid.IABatch(files: batchFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RD files array
|
// RD files array
|
||||||
// Possibly sort this in the future, but not sure how at the moment
|
// Possibly sort this in the future, but not sure how at the moment
|
||||||
var files: [RealDebridIAFile] = []
|
var files: [RealDebrid.IAFile] = []
|
||||||
|
|
||||||
for index in batches.indices {
|
for index in batches.indices {
|
||||||
let batchFiles = batches[index].files
|
let batchFiles = batches[index].files
|
||||||
|
|
@ -239,7 +228,7 @@ public class RealDebrid {
|
||||||
|
|
||||||
if !files.contains(where: { $0.name == batchFile.fileName }) {
|
if !files.contains(where: { $0.name == batchFile.fileName }) {
|
||||||
files.append(
|
files.append(
|
||||||
RealDebridIAFile(
|
RealDebrid.IAFile(
|
||||||
name: batchFile.fileName,
|
name: batchFile.fileName,
|
||||||
batchIndex: index,
|
batchIndex: index,
|
||||||
batchFileIndex: batchFileIndex
|
batchFileIndex: batchFileIndex
|
||||||
|
|
@ -251,7 +240,7 @@ public class RealDebrid {
|
||||||
|
|
||||||
// TTL: 5 minutes
|
// TTL: 5 minutes
|
||||||
availableHashes.append(
|
availableHashes.append(
|
||||||
RealDebridIA(
|
RealDebrid.IA(
|
||||||
hash: hash,
|
hash: hash,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300,
|
||||||
files: files,
|
files: files,
|
||||||
|
|
@ -260,7 +249,7 @@ public class RealDebrid {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
availableHashes.append(
|
availableHashes.append(
|
||||||
RealDebridIA(
|
RealDebrid.IA(
|
||||||
hash: hash,
|
hash: hash,
|
||||||
expiryTimeStamp: Date().timeIntervalSince1970 + 300
|
expiryTimeStamp: Date().timeIntervalSince1970 + 300
|
||||||
)
|
)
|
||||||
|
|
@ -319,9 +308,9 @@ public class RealDebrid {
|
||||||
if let torrentLink = rawResponse.links[safe: selectedIndex ?? -1], rawResponse.status == "downloaded" {
|
if let torrentLink = rawResponse.links[safe: selectedIndex ?? -1], rawResponse.status == "downloaded" {
|
||||||
return torrentLink
|
return torrentLink
|
||||||
} else if rawResponse.status == "downloading" || rawResponse.status == "queued" {
|
} else if rawResponse.status == "downloading" || rawResponse.status == "queued" {
|
||||||
throw RealDebridError.EmptyTorrents
|
throw RDError.EmptyTorrents
|
||||||
} else {
|
} else {
|
||||||
throw RealDebridError.EmptyData
|
throw RDError.EmptyData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct GithubRelease: Codable, Hashable, Sendable {
|
extension Github {
|
||||||
let htmlUrl: String
|
public struct Release: Codable, Hashable, Sendable {
|
||||||
let tagName: String
|
let htmlUrl: String
|
||||||
|
let tagName: String
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case htmlUrl = "html_url"
|
case htmlUrl = "html_url"
|
||||||
case tagName = "tag_name"
|
case tagName = "tag_name"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,187 +8,201 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// MARK: - device code endpoint
|
extension RealDebrid {
|
||||||
|
// MARK: - Errors
|
||||||
public struct DeviceCodeResponse: Codable, Sendable {
|
public enum RDError: Error {
|
||||||
let deviceCode, userCode: String
|
case InvalidUrl
|
||||||
let interval, expiresIn: Int
|
case InvalidPostBody
|
||||||
let verificationURL, directVerificationURL: String
|
case InvalidResponse
|
||||||
|
case InvalidToken
|
||||||
enum CodingKeys: String, CodingKey {
|
case EmptyData
|
||||||
case deviceCode = "device_code"
|
case EmptyTorrents
|
||||||
case userCode = "user_code"
|
case FailedRequest(description: String)
|
||||||
case interval
|
case AuthQuery(description: String)
|
||||||
case expiresIn = "expires_in"
|
|
||||||
case verificationURL = "verification_url"
|
|
||||||
case directVerificationURL = "direct_verification_url"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - device credentials endpoint
|
// MARK: - device code endpoint
|
||||||
|
|
||||||
public struct DeviceCredentialsResponse: Codable, Sendable {
|
public struct DeviceCodeResponse: Codable, Sendable {
|
||||||
let clientID, clientSecret: String?
|
let deviceCode, userCode: String
|
||||||
|
let interval, expiresIn: Int
|
||||||
|
let verificationURL, directVerificationURL: String
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case clientID = "client_id"
|
case deviceCode = "device_code"
|
||||||
case clientSecret = "client_secret"
|
case userCode = "user_code"
|
||||||
|
case interval
|
||||||
|
case expiresIn = "expires_in"
|
||||||
|
case verificationURL = "verification_url"
|
||||||
|
case directVerificationURL = "direct_verification_url"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - token endpoint
|
// MARK: - device credentials endpoint
|
||||||
|
|
||||||
public struct TokenResponse: Codable, Sendable {
|
public struct DeviceCredentialsResponse: Codable, Sendable {
|
||||||
let accessToken: String
|
let clientID, clientSecret: String?
|
||||||
let expiresIn: Int
|
|
||||||
let refreshToken, tokenType: String
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case accessToken = "access_token"
|
case clientID = "client_id"
|
||||||
case expiresIn = "expires_in"
|
case clientSecret = "client_secret"
|
||||||
case refreshToken = "refresh_token"
|
}
|
||||||
case tokenType = "token_type"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - instantAvailability endpoint
|
// MARK: - token endpoint
|
||||||
|
|
||||||
// Thanks Skitty!
|
public struct TokenResponse: Codable, Sendable {
|
||||||
public struct InstantAvailabilityResponse: Codable, Sendable {
|
let accessToken: String
|
||||||
var data: InstantAvailabilityData?
|
let expiresIn: Int
|
||||||
|
let refreshToken, tokenType: String
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
enum CodingKeys: String, CodingKey {
|
||||||
let container = try decoder.singleValueContainer()
|
case accessToken = "access_token"
|
||||||
|
case expiresIn = "expires_in"
|
||||||
|
case refreshToken = "refresh_token"
|
||||||
|
case tokenType = "token_type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let data = try? container.decode(InstantAvailabilityData.self) {
|
// MARK: - instantAvailability endpoint
|
||||||
self.data = data
|
|
||||||
|
// Thanks Skitty!
|
||||||
|
public struct InstantAvailabilityResponse: Codable, Sendable {
|
||||||
|
var data: InstantAvailabilityData?
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
if let data = try? container.decode(InstantAvailabilityData.self) {
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstantAvailabilityData: Codable, Sendable {
|
||||||
|
var rd: [[String: InstantAvailabilityInfo]]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstantAvailabilityInfo: Codable, Sendable {
|
||||||
|
var filename: String
|
||||||
|
var filesize: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Instant Availability client side structures
|
||||||
|
|
||||||
|
public struct IA: Codable, Hashable, Sendable {
|
||||||
|
let hash: String
|
||||||
|
let expiryTimeStamp: Double
|
||||||
|
var files: [IAFile] = []
|
||||||
|
var batches: [IABatch] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IABatch: Codable, Hashable, Sendable {
|
||||||
|
let files: [IABatchFile]
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IABatchFile: Codable, Hashable, Sendable {
|
||||||
|
let id: Int
|
||||||
|
let fileName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IAFile: Codable, Hashable, Sendable {
|
||||||
|
let name: String
|
||||||
|
let batchIndex: Int
|
||||||
|
let batchFileIndex: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum IAStatus: Codable, Hashable, Sendable {
|
||||||
|
case full
|
||||||
|
case partial
|
||||||
|
case none
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - addMagnet endpoint
|
||||||
|
|
||||||
|
public struct AddMagnetResponse: Codable, Sendable {
|
||||||
|
let id: String
|
||||||
|
let uri: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - torrentInfo endpoint
|
||||||
|
|
||||||
|
struct TorrentInfoResponse: Codable, Sendable {
|
||||||
|
let id, filename, originalFilename, hash: String
|
||||||
|
let bytes, originalBytes: Int
|
||||||
|
let host: String
|
||||||
|
let split, progress: Int
|
||||||
|
let status, added: String
|
||||||
|
let files: [TorrentInfoFile]
|
||||||
|
let links: [String]
|
||||||
|
let ended: String?
|
||||||
|
let speed: Int?
|
||||||
|
let seeders: Int?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, filename
|
||||||
|
case originalFilename = "original_filename"
|
||||||
|
case hash, bytes
|
||||||
|
case originalBytes = "original_bytes"
|
||||||
|
case host, split, progress, status, added, files, links, ended, speed, seeders
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TorrentInfoFile: Codable, Sendable {
|
||||||
|
let id: Int
|
||||||
|
let path: String
|
||||||
|
let bytes, selected: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct UserTorrentsResponse: Codable, Sendable {
|
||||||
|
let id, filename, hash: String
|
||||||
|
let bytes: Int
|
||||||
|
let host: String
|
||||||
|
let split, progress: Int
|
||||||
|
let status, added: String
|
||||||
|
let links: [String]
|
||||||
|
let speed, seeders: Int?
|
||||||
|
let ended: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - unrestrictLink endpoint
|
||||||
|
|
||||||
|
struct UnrestrictLinkResponse: Codable, Sendable {
|
||||||
|
let id, filename: String
|
||||||
|
let mimeType: String?
|
||||||
|
let filesize: Int
|
||||||
|
let link: String
|
||||||
|
let host: String
|
||||||
|
let hostIcon: String
|
||||||
|
let chunks, crc: Int
|
||||||
|
let download: String
|
||||||
|
let streamable: Int
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, filename, mimeType, filesize, link, host
|
||||||
|
case hostIcon = "host_icon"
|
||||||
|
case chunks, crc, download, streamable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - User downloads list
|
||||||
|
|
||||||
|
public struct UserDownloadsResponse: Codable, Sendable {
|
||||||
|
let id, filename: String
|
||||||
|
let mimeType: String?
|
||||||
|
let filesize: Int
|
||||||
|
let link: String
|
||||||
|
let host: String
|
||||||
|
let hostIcon: String
|
||||||
|
let chunks: Int
|
||||||
|
let download: String
|
||||||
|
let streamable: Int
|
||||||
|
let generated: String
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, filename, mimeType, filesize, link, host
|
||||||
|
case hostIcon = "host_icon"
|
||||||
|
case chunks, download, streamable, generated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Instant Availability client side structures
|
|
||||||
|
|
||||||
struct InstantAvailabilityData: Codable, Sendable {
|
|
||||||
var rd: [[String: InstantAvailabilityInfo]]
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InstantAvailabilityInfo: Codable, Sendable {
|
|
||||||
var filename: String
|
|
||||||
var filesize: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct RealDebridIA: Codable, Hashable, Sendable {
|
|
||||||
let hash: String
|
|
||||||
let expiryTimeStamp: Double
|
|
||||||
var files: [RealDebridIAFile] = []
|
|
||||||
var batches: [RealDebridIABatch] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct RealDebridIABatch: Codable, Hashable, Sendable {
|
|
||||||
let files: [RealDebridIABatchFile]
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct RealDebridIABatchFile: Codable, Hashable, Sendable {
|
|
||||||
let id: Int
|
|
||||||
let fileName: String
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct RealDebridIAFile: Codable, Hashable, Sendable {
|
|
||||||
let name: String
|
|
||||||
let batchIndex: Int
|
|
||||||
let batchFileIndex: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum RealDebridIAStatus: Codable, Hashable, Sendable {
|
|
||||||
case full
|
|
||||||
case partial
|
|
||||||
case none
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - addMagnet endpoint
|
|
||||||
|
|
||||||
public struct AddMagnetResponse: Codable, Sendable {
|
|
||||||
let id: String
|
|
||||||
let uri: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - torrentInfo endpoint
|
|
||||||
|
|
||||||
struct TorrentInfoResponse: Codable, Sendable {
|
|
||||||
let id, filename, originalFilename, hash: String
|
|
||||||
let bytes, originalBytes: Int
|
|
||||||
let host: String
|
|
||||||
let split, progress: Int
|
|
||||||
let status, added: String
|
|
||||||
let files: [TorrentInfoFile]
|
|
||||||
let links: [String]
|
|
||||||
let ended: String?
|
|
||||||
let speed: Int?
|
|
||||||
let seeders: Int?
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case id, filename
|
|
||||||
case originalFilename = "original_filename"
|
|
||||||
case hash, bytes
|
|
||||||
case originalBytes = "original_bytes"
|
|
||||||
case host, split, progress, status, added, files, links, ended, speed, seeders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TorrentInfoFile: Codable, Sendable {
|
|
||||||
let id: Int
|
|
||||||
let path: String
|
|
||||||
let bytes, selected: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct UserTorrentsResponse: Codable, Sendable {
|
|
||||||
let id, filename, hash: String
|
|
||||||
let bytes: Int
|
|
||||||
let host: String
|
|
||||||
let split, progress: Int
|
|
||||||
let status, added: String
|
|
||||||
let links: [String]
|
|
||||||
let speed, seeders: Int?
|
|
||||||
let ended: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - unrestrictLink endpoint
|
|
||||||
|
|
||||||
struct UnrestrictLinkResponse: Codable, Sendable {
|
|
||||||
let id, filename: String
|
|
||||||
let mimeType: String?
|
|
||||||
let filesize: Int
|
|
||||||
let link: String
|
|
||||||
let host: String
|
|
||||||
let hostIcon: String
|
|
||||||
let chunks, crc: Int
|
|
||||||
let download: String
|
|
||||||
let streamable: Int
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case id, filename, mimeType, filesize, link, host
|
|
||||||
case hostIcon = "host_icon"
|
|
||||||
case chunks, crc, download, streamable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - User downloads list
|
|
||||||
|
|
||||||
public struct UserDownloadsResponse: Codable, Sendable {
|
|
||||||
let id, filename: String
|
|
||||||
let mimeType: String?
|
|
||||||
let filesize: Int
|
|
||||||
let link: String
|
|
||||||
let host: String
|
|
||||||
let hostIcon: String
|
|
||||||
let chunks: Int
|
|
||||||
let download: String
|
|
||||||
let streamable: Int
|
|
||||||
let generated: String
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case id, filename, mimeType, filesize, link, host
|
|
||||||
case hostIcon = "host_icon"
|
|
||||||
case chunks, download, streamable, generated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,14 @@ public class DebridManager: ObservableObject {
|
||||||
var realDebridAuthUrl: String = ""
|
var realDebridAuthUrl: String = ""
|
||||||
|
|
||||||
// RealDebrid fetch variables
|
// RealDebrid fetch variables
|
||||||
@Published var realDebridIAValues: [RealDebridIA] = []
|
@Published var realDebridIAValues: [RealDebrid.IA] = []
|
||||||
var realDebridDownloadUrl: String = ""
|
var realDebridDownloadUrl: String = ""
|
||||||
|
|
||||||
@Published var showDeleteAlert: Bool = false
|
@Published var showDeleteAlert: Bool = false
|
||||||
|
|
||||||
// TODO: Switch to an individual item based sheet system to remove these variables
|
// TODO: Switch to an individual item based sheet system to remove these variables
|
||||||
var selectedRealDebridItem: RealDebridIA?
|
var selectedRealDebridItem: RealDebrid.IA?
|
||||||
var selectedRealDebridFile: RealDebridIAFile?
|
var selectedRealDebridFile: RealDebrid.IAFile?
|
||||||
var selectedRealDebridID: String?
|
var selectedRealDebridID: String?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -81,7 +81,7 @@ public class DebridManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func matchSearchResult(result: SearchResult?) -> RealDebridIAStatus {
|
public func matchSearchResult(result: SearchResult?) -> RealDebrid.IAStatus {
|
||||||
guard let result else {
|
guard let result else {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +202,7 @@ public class DebridManager: ObservableObject {
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
switch error {
|
switch error {
|
||||||
case RealDebridError.EmptyTorrents:
|
case RealDebrid.RDError.EmptyTorrents:
|
||||||
showDeleteAlert.toggle()
|
showDeleteAlert.toggle()
|
||||||
default:
|
default:
|
||||||
let error = error as NSError
|
let error = error as NSError
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ struct SettingsAppVersionView: View {
|
||||||
@EnvironmentObject var toastModel: ToastViewModel
|
@EnvironmentObject var toastModel: ToastViewModel
|
||||||
|
|
||||||
@State private var viewTask: Task<Void, Never>?
|
@State private var viewTask: Task<Void, Never>?
|
||||||
@State private var releases: [GithubRelease] = []
|
@State private var releases: [Github.Release] = []
|
||||||
|
|
||||||
@State private var loadedReleases = false
|
@State private var loadedReleases = false
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue