RealDebrid: Add universal request wrapper
Every request has to add an authentication header. If the response is 401 (Unauthorized), log the user out of RealDebrid. Signed-off-by: kingbri <bdashore3@gmail.com>
This commit is contained in:
parent
c2f267dbc3
commit
4a53eb052c
2 changed files with 60 additions and 113 deletions
|
|
@ -14,12 +14,8 @@ public enum RealDebridError: Error {
|
||||||
case InvalidResponse
|
case InvalidResponse
|
||||||
case InvalidToken
|
case InvalidToken
|
||||||
case EmptyData
|
case EmptyData
|
||||||
|
case FailedRequest(description: String)
|
||||||
case AuthQuery(description: String)
|
case AuthQuery(description: String)
|
||||||
case InstantAvailabilityQuery(description: String)
|
|
||||||
case AddMagnetQuery(description: String)
|
|
||||||
case SelectFilesQuery(description: String)
|
|
||||||
case TorrentInfoQuery(description: String)
|
|
||||||
case UnrestrictLinkQuery(description: String)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RealDebrid: ObservableObject {
|
public class RealDebrid: ObservableObject {
|
||||||
|
|
@ -57,11 +53,11 @@ public class RealDebrid: ObservableObject {
|
||||||
do {
|
do {
|
||||||
try await getDeviceCredentials(deviceCode: rawResponse.deviceCode)
|
try await getDeviceCredentials(deviceCode: rawResponse.deviceCode)
|
||||||
} catch {
|
} catch {
|
||||||
print("Authentication error: \(error)")
|
print("Authentication error in \(#function): \(error)")
|
||||||
authTask?.cancel()
|
authTask?.cancel()
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
parentManager?.toastModel?.toastDescription = "Authentication error: \(error)"
|
parentManager?.toastModel?.toastDescription = "Authentication error in \(#function): \(error)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,9 +109,8 @@ public class RealDebrid: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .failure(error) = await authTask?.result {
|
if case let .failure(error) = await authTask?.result {
|
||||||
print(error)
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,45 +193,45 @@ public class RealDebrid: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the magnet is streamable on RD
|
// Wrapper request function which matches the responses and returns data
|
||||||
// Currently does not work for batch links
|
@discardableResult public func performRequest(request: inout URLRequest, requestName: String) async throws -> Data {
|
||||||
public func instantAvailability(magnetHashes: [String]) async -> [String]? {
|
|
||||||
var availableHashes: [String] = []
|
|
||||||
|
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnetHashes.joined(separator: "/"))")!)
|
|
||||||
|
|
||||||
guard let token = await fetchToken() else {
|
guard let token = await fetchToken() else {
|
||||||
return nil
|
throw RealDebridError.InvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
|
||||||
do {
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
// Assume that RealDebrid can be called here
|
|
||||||
let (data, response) = try await URLSession.shared.data(for: request)
|
|
||||||
|
|
||||||
// Unauthorized, auto-logout of RD, wrap this into a request function
|
guard let response = response as? HTTPURLResponse else {
|
||||||
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 401 {
|
throw RealDebridError.FailedRequest(description: "No HTTP response given")
|
||||||
try await deleteTokens()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Does not account for torrent packs at the moment
|
if response.statusCode >= 200, response.statusCode <= 299 {
|
||||||
if let rawResponse = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
return data
|
||||||
for (key, value) in rawResponse {
|
} else if response.statusCode == 401 {
|
||||||
if value as? [String: Any] != nil {
|
try await deleteTokens()
|
||||||
availableHashes.append(key)
|
throw RealDebridError.FailedRequest(description: "The request \(requestName) failed because you were unauthorized. Please relogin to RealDebrid in Settings.")
|
||||||
}
|
} else {
|
||||||
|
throw RealDebridError.FailedRequest(description: "The request \(requestName) failed with status code \(response.statusCode).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the magnet is streamable on RD
|
||||||
|
// Currently does not work for batch links
|
||||||
|
public func instantAvailability(magnetHashes: [String]) async throws -> [String] {
|
||||||
|
var availableHashes: [String] = []
|
||||||
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnetHashes.joined(separator: "/"))")!)
|
||||||
|
|
||||||
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
|
|
||||||
|
// Does not account for torrent packs at the moment
|
||||||
|
if let rawResponse = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||||
|
for (key, value) in rawResponse {
|
||||||
|
if value as? [String: Any] != nil {
|
||||||
|
availableHashes.append(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// Assume that RealDebrid cannot be used here
|
|
||||||
print("RealDebrid request error: \(error)")
|
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
parentManager?.toastModel?.toastDescription = "RealDebrid InstantAvailability error: \(error)"
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableHashes
|
return availableHashes
|
||||||
|
|
@ -245,14 +240,7 @@ public class RealDebrid: ObservableObject {
|
||||||
// Adds a magnet link to the user's RD account
|
// Adds a magnet link to the user's RD account
|
||||||
public func addMagnet(magnetLink: String) async throws -> String {
|
public func addMagnet(magnetLink: String) async throws -> String {
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/addMagnet")!)
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/addMagnet")!)
|
||||||
|
|
||||||
guard let token = await fetchToken() else {
|
|
||||||
throw RealDebridError.InvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
request.httpMethod = "POST"
|
request.httpMethod = "POST"
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
@ -260,28 +248,16 @@ public class RealDebrid: ObservableObject {
|
||||||
|
|
||||||
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
||||||
|
|
||||||
do {
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
let rawResponse = try jsonDecoder.decode(AddMagnetResponse.self, from: data)
|
||||||
let rawResponse = try jsonDecoder.decode(AddMagnetResponse.self, from: data)
|
|
||||||
|
|
||||||
return rawResponse.id
|
return rawResponse.id
|
||||||
} catch {
|
|
||||||
print("Magnet link query error! \(error)")
|
|
||||||
throw RealDebridError.AddMagnetQuery(description: error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queues the magnet link for downloading
|
// Queues the magnet link for downloading
|
||||||
public func selectFiles(debridID: String) async throws -> HTTPURLResponse? {
|
public func selectFiles(debridID: String) async throws {
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/selectFiles/\(debridID)")!)
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/selectFiles/\(debridID)")!)
|
||||||
|
|
||||||
guard let token = await fetchToken() else {
|
|
||||||
throw RealDebridError.InvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
request.httpMethod = "POST"
|
request.httpMethod = "POST"
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
@ -289,52 +265,27 @@ public class RealDebrid: ObservableObject {
|
||||||
|
|
||||||
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
||||||
|
|
||||||
do {
|
try await performRequest(request: &request, requestName: #function)
|
||||||
let (_, response) = try await URLSession.shared.data(for: request)
|
|
||||||
|
|
||||||
return response as? HTTPURLResponse
|
|
||||||
} catch {
|
|
||||||
print("Magnet file query error! \(error)")
|
|
||||||
throw RealDebridError.SelectFilesQuery(description: error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the info of a torrent
|
// Fetches the info of a torrent
|
||||||
public func torrentInfo(debridID: String) async throws -> String {
|
public func torrentInfo(debridID: String) async throws -> String {
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!)
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!)
|
||||||
|
|
||||||
guard let token = await fetchToken() else {
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
throw RealDebridError.InvalidToken
|
let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data)
|
||||||
}
|
|
||||||
|
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
if let torrentLink = rawResponse.links[safe: 0] {
|
||||||
|
return torrentLink
|
||||||
do {
|
} else {
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
throw RealDebridError.EmptyData
|
||||||
let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data)
|
|
||||||
|
|
||||||
if let torrentLink = rawResponse.links[safe: 0] {
|
|
||||||
return torrentLink
|
|
||||||
} else {
|
|
||||||
throw RealDebridError.EmptyData
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Torrent info query error: \(error)")
|
|
||||||
throw RealDebridError.TorrentInfoQuery(description: error.localizedDescription)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downloads link from selectFiles for playback
|
// Downloads link from selectFiles for playback
|
||||||
public func unrestrictLink(debridDownloadLink: String) async throws -> String {
|
public func unrestrictLink(debridDownloadLink: String) async throws -> String {
|
||||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/unrestrict/link")!)
|
var request = URLRequest(url: URL(string: "\(baseApiUrl)/unrestrict/link")!)
|
||||||
|
|
||||||
guard let token = await fetchToken() else {
|
|
||||||
throw RealDebridError.InvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
request.httpMethod = "POST"
|
request.httpMethod = "POST"
|
||||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
@ -342,14 +293,9 @@ public class RealDebrid: ObservableObject {
|
||||||
|
|
||||||
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
request.httpBody = bodyComponents.query?.data(using: .utf8)
|
||||||
|
|
||||||
do {
|
let data = try await performRequest(request: &request, requestName: #function)
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
let rawResponse = try jsonDecoder.decode(UnrestrictLinkResponse.self, from: data)
|
||||||
let rawResponse = try jsonDecoder.decode(UnrestrictLinkResponse.self, from: data)
|
|
||||||
|
|
||||||
return rawResponse.download
|
return rawResponse.download
|
||||||
} catch {
|
|
||||||
print("Unrestrict link error: \(error)")
|
|
||||||
throw RealDebridError.UnrestrictLinkQuery(description: error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,18 @@ public class DebridManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let debridHashes = await realDebrid.instantAvailability(magnetHashes: hashes) else {
|
do {
|
||||||
return
|
let debridHashes = try await realDebrid.instantAvailability(magnetHashes: hashes)
|
||||||
}
|
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
realDebridHashes = debridHashes
|
realDebridHashes = debridHashes
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Task { @MainActor in
|
||||||
|
toastModel?.toastDescription = "RealDebrid hash error: \(error)"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,12 +70,7 @@ public class DebridManager: ObservableObject {
|
||||||
public func fetchRdDownload(searchResult: SearchResult) async {
|
public func fetchRdDownload(searchResult: SearchResult) async {
|
||||||
do {
|
do {
|
||||||
let realDebridId = try await realDebrid.addMagnet(magnetLink: searchResult.magnetLink)
|
let realDebridId = try await realDebrid.addMagnet(magnetLink: searchResult.magnetLink)
|
||||||
let httpResponse = try await realDebrid.selectFiles(debridID: realDebridId)
|
try await realDebrid.selectFiles(debridID: realDebridId)
|
||||||
|
|
||||||
if httpResponse?.statusCode != 204 {
|
|
||||||
// Throw error here
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId)
|
let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId)
|
||||||
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
|
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue