mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
Tree: Cleanup access levels
Public should not be used in an app since it declares public to additional modules. However, an app is one module. Some structs/ classes need to be left public to conform to CoreData's generation. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
3b771e5deb
commit
ecdd0199f6
32 changed files with 258 additions and 267 deletions
|
|
@ -7,19 +7,18 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
// TODO: Fix errors
|
||||
public class AllDebrid: PollingDebridSource, ObservableObject {
|
||||
public let id = "AllDebrid"
|
||||
public let abbreviation = "AD"
|
||||
public let website = "https://alldebrid.com"
|
||||
public var authTask: Task<Void, Error>?
|
||||
class AllDebrid: PollingDebridSource, ObservableObject {
|
||||
let id = "AllDebrid"
|
||||
let abbreviation = "AD"
|
||||
let website = "https://alldebrid.com"
|
||||
var authTask: Task<Void, Error>?
|
||||
|
||||
public var authProcessing: Bool = false
|
||||
public var isLoggedIn: Bool {
|
||||
var authProcessing: Bool = false
|
||||
var isLoggedIn: Bool {
|
||||
getToken() != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "AllDebrid.UseManualKey") {
|
||||
return getToken()
|
||||
} else {
|
||||
|
|
@ -27,20 +26,20 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
public var cloudTTL: Double = 0.0
|
||||
@Published var IAValues: [DebridIA] = []
|
||||
@Published var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published var cloudTorrents: [DebridCloudTorrent] = []
|
||||
var cloudTTL: Double = 0.0
|
||||
|
||||
let baseApiUrl = "https://api.alldebrid.com/v4"
|
||||
let appName = "Ferrite"
|
||||
private let baseApiUrl = "https://api.alldebrid.com/v4"
|
||||
private let appName = "Ferrite"
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
private let jsonDecoder = JSONDecoder()
|
||||
|
||||
// MARK: - Auth
|
||||
|
||||
// Fetches information for PIN auth
|
||||
public func getAuthUrl() async throws -> URL {
|
||||
func getAuthUrl() async throws -> URL {
|
||||
let url = try buildRequestURL(urlString: "\(baseApiUrl)/pin/get")
|
||||
let request = URLRequest(url: url)
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Fetches API keys
|
||||
public func getApiKey(checkID: String, pin: String) async throws {
|
||||
func getApiKey(checkID: String, pin: String) async throws {
|
||||
let queryItems = [
|
||||
URLQueryItem(name: "agent", value: appName),
|
||||
URLQueryItem(name: "check", value: checkID),
|
||||
|
|
@ -109,17 +108,17 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a manual API key instead of web auth
|
||||
public func setApiKey(_ key: String) {
|
||||
func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "AllDebrid.ApiKey")
|
||||
UserDefaults.standard.set(true, forKey: "AllDebrid.UseManualKey")
|
||||
}
|
||||
|
||||
public func getToken() -> String? {
|
||||
func getToken() -> String? {
|
||||
FerriteKeychain.shared.get("AllDebrid.ApiKey")
|
||||
}
|
||||
|
||||
// Clears tokens. No endpoint to deregister a device
|
||||
public func logout() {
|
||||
func logout() {
|
||||
FerriteKeychain.shared.delete("AllDebrid.ApiKey")
|
||||
UserDefaults.standard.removeObject(forKey: "AllDebrid.UseManualKey")
|
||||
}
|
||||
|
|
@ -150,7 +149,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Builds a URL for further requests
|
||||
private func buildRequestURL(urlString: String, queryItems: [URLQueryItem] = []) throws -> URL {
|
||||
func buildRequestURL(urlString: String, queryItems: [URLQueryItem] = []) throws -> URL {
|
||||
guard var components = URLComponents(string: urlString) else {
|
||||
throw DebridError.InvalidUrl
|
||||
}
|
||||
|
|
@ -168,7 +167,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
|
||||
// MARK: - Instant availability
|
||||
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
func instantAvailability(magnets: [Magnet]) async throws {
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
let sendMagnets = magnets.filter { magnet in
|
||||
|
|
@ -215,7 +214,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Downloading
|
||||
|
||||
// Wrapper function to fetch a download link from the API
|
||||
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
let selectedMagnetId: String
|
||||
|
||||
if let existingMagnet = cloudTorrents.first(where: { $0.hash == magnet.hash && $0.status == "Ready" }) {
|
||||
|
|
@ -237,7 +236,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a magnet link to the user's AD account
|
||||
public func addMagnet(magnet: Magnet) async throws -> Int {
|
||||
func addMagnet(magnet: Magnet) async throws -> Int {
|
||||
guard let magnetLink = magnet.link else {
|
||||
throw DebridError.FailedRequest(description: "The magnet link is invalid")
|
||||
}
|
||||
|
|
@ -263,7 +262,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> String {
|
||||
func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> String {
|
||||
let queryItems = [
|
||||
URLQueryItem(name: "id", value: magnetId)
|
||||
]
|
||||
|
|
@ -280,7 +279,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func unlockLink(lockedLink: String) async throws -> String {
|
||||
func unlockLink(lockedLink: String) async throws -> String {
|
||||
let queryItems = [
|
||||
URLQueryItem(name: "link", value: lockedLink)
|
||||
]
|
||||
|
|
@ -292,7 +291,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
return rawResponse.link
|
||||
}
|
||||
|
||||
public func saveLink(link: String) async throws {
|
||||
func saveLink(link: String) async throws {
|
||||
let queryItems = [
|
||||
URLQueryItem(name: "links[]", value: link)
|
||||
]
|
||||
|
|
@ -304,7 +303,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Cloud methods
|
||||
|
||||
// Referred to as "User magnets" in AllDebrid's API
|
||||
public func getUserTorrents() async throws {
|
||||
func getUserTorrents() async throws {
|
||||
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status"))
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -326,7 +325,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func deleteTorrent(torrentId: String?) async throws {
|
||||
func deleteTorrent(torrentId: String?) async throws {
|
||||
guard let torrentId else {
|
||||
throw DebridError.FailedRequest(description: "The torrentID \(String(describing: torrentId)) is invalid")
|
||||
}
|
||||
|
|
@ -339,7 +338,7 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
try await performRequest(request: &request, requestName: #function)
|
||||
}
|
||||
|
||||
public func getUserDownloads() async throws {
|
||||
func getUserDownloads() async throws {
|
||||
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/user/links"))
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -358,12 +357,12 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Not used
|
||||
public func checkUserDownloads(link: String) async throws -> String? {
|
||||
func checkUserDownloads(link: String) async throws -> String? {
|
||||
nil
|
||||
}
|
||||
|
||||
// The downloadId is actually the download link
|
||||
public func deleteDownload(downloadId: String) async throws {
|
||||
func deleteDownload(downloadId: String) async throws {
|
||||
let queryItems = [
|
||||
URLQueryItem(name: "link", value: downloadId)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Github {
|
||||
public func fetchLatestRelease() async throws -> Release? {
|
||||
class Github {
|
||||
func fetchLatestRelease() async throws -> Release? {
|
||||
let url = URL(string: "https://api.github.com/repos/Ferrite-iOS/Ferrite/releases/latest")!
|
||||
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
|
|
@ -17,7 +17,7 @@ public class Github {
|
|||
return rawResponse
|
||||
}
|
||||
|
||||
public func fetchReleases() async throws -> [Release]? {
|
||||
func fetchReleases() async throws -> [Release]? {
|
||||
let url = URL(string: "https://api.github.com/repos/Ferrite-iOS/Ferrite/releases")!
|
||||
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Kodi {
|
||||
let encoder = JSONEncoder()
|
||||
class Kodi {
|
||||
private let encoder = JSONEncoder()
|
||||
|
||||
// Used to add server to CoreData. Not part of API
|
||||
public func addServer(urlString: String,
|
||||
func addServer(urlString: String,
|
||||
friendlyName: String?,
|
||||
username: String?,
|
||||
password: String?,
|
||||
|
|
@ -65,7 +65,7 @@ public class Kodi {
|
|||
try backgroundContext.save()
|
||||
}
|
||||
|
||||
public func ping(server: KodiServer) async throws {
|
||||
func ping(server: KodiServer) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(server.urlString)/jsonrpc")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
|
@ -94,7 +94,7 @@ public class Kodi {
|
|||
}
|
||||
}
|
||||
|
||||
public func sendVideoUrl(urlString: String, server: KodiServer) async throws {
|
||||
func sendVideoUrl(urlString: String, server: KodiServer) async throws {
|
||||
if URL(string: urlString) == nil {
|
||||
throw KodiError.InvalidPlaybackUrl
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,17 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Premiumize: OAuthDebridSource, ObservableObject {
|
||||
public let id = "Premiumize"
|
||||
public let abbreviation = "PM"
|
||||
public let website = "https://premiumize.me"
|
||||
@Published public var authProcessing: Bool = false
|
||||
public var isLoggedIn: Bool {
|
||||
class Premiumize: OAuthDebridSource, ObservableObject {
|
||||
let id = "Premiumize"
|
||||
let abbreviation = "PM"
|
||||
let website = "https://premiumize.me"
|
||||
|
||||
@Published var authProcessing: Bool = false
|
||||
var isLoggedIn: Bool {
|
||||
getToken() != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "Premiumize.UseManualKey") {
|
||||
return getToken()
|
||||
} else {
|
||||
|
|
@ -24,20 +25,20 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
public var cloudTTL: Double = 0.0
|
||||
@Published var IAValues: [DebridIA] = []
|
||||
@Published var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published var cloudTorrents: [DebridCloudTorrent] = []
|
||||
var cloudTTL: Double = 0.0
|
||||
|
||||
let baseAuthUrl = "https://www.premiumize.me/authorize"
|
||||
let baseApiUrl = "https://www.premiumize.me/api"
|
||||
let clientId = "791565696"
|
||||
private let baseAuthUrl = "https://www.premiumize.me/authorize"
|
||||
private let baseApiUrl = "https://www.premiumize.me/api"
|
||||
private let clientId = "791565696"
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
private let jsonDecoder = JSONDecoder()
|
||||
|
||||
// MARK: - Auth
|
||||
|
||||
public func getAuthUrl() throws -> URL {
|
||||
func getAuthUrl() throws -> URL {
|
||||
var urlComponents = URLComponents(string: baseAuthUrl)!
|
||||
urlComponents.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: clientId),
|
||||
|
|
@ -52,7 +53,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func handleAuthCallback(url: URL) throws {
|
||||
func handleAuthCallback(url: URL) throws {
|
||||
let callbackComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
||||
|
||||
guard let callbackFragment = callbackComponents?.fragment else {
|
||||
|
|
@ -70,17 +71,17 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a manual API key instead of web auth
|
||||
public func setApiKey(_ key: String) {
|
||||
func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "Premiumize.AccessToken")
|
||||
UserDefaults.standard.set(true, forKey: "Premiumize.UseManualKey")
|
||||
}
|
||||
|
||||
public func getToken() -> String? {
|
||||
func getToken() -> String? {
|
||||
FerriteKeychain.shared.get("Premiumize.AccessToken")
|
||||
}
|
||||
|
||||
// Clears tokens. No endpoint to deregister a device
|
||||
public func logout() {
|
||||
func logout() {
|
||||
FerriteKeychain.shared.delete("Premiumize.AccessToken")
|
||||
UserDefaults.standard.removeObject(forKey: "Premiumize.UseManualKey")
|
||||
}
|
||||
|
|
@ -132,7 +133,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
|
||||
// MARK: - Instant availability
|
||||
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
func instantAvailability(magnets: [Magnet]) async throws {
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
// Remove magnets that don't have an associated link for PM along with existing TTL logic
|
||||
|
|
@ -168,7 +169,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
|
||||
// Function to divide and execute DDL endpoint requests in parallel
|
||||
// Calls this for 10 requests at a time to not overwhelm API servers
|
||||
public func divideDDLRequests(magnetChunk: [Magnet]) async throws -> [DebridIA] {
|
||||
func divideDDLRequests(magnetChunk: [Magnet]) async throws -> [DebridIA] {
|
||||
let tempIA = try await withThrowingTaskGroup(of: DebridIA.self) { group in
|
||||
for magnet in magnetChunk {
|
||||
group.addTask {
|
||||
|
|
@ -187,7 +188,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Grabs DDL links
|
||||
func fetchDDL(magnet: Magnet) async throws -> DebridIA {
|
||||
private func fetchDDL(magnet: Magnet) async throws -> DebridIA {
|
||||
if magnet.hash == nil {
|
||||
throw DebridError.EmptyData
|
||||
}
|
||||
|
|
@ -227,7 +228,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
|
||||
// Function to divide and execute cache endpoint requests in parallel
|
||||
// Calls this for 100 hashes at a time due to API limits
|
||||
public func divideCacheRequests(magnets: [Magnet]) async throws -> [Magnet] {
|
||||
func divideCacheRequests(magnets: [Magnet]) async throws -> [Magnet] {
|
||||
let availableMagnets = try await withThrowingTaskGroup(of: [Magnet].self) { group in
|
||||
for chunk in magnets.chunked(into: 100) {
|
||||
group.addTask {
|
||||
|
|
@ -247,7 +248,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Parent function for initial checking of the cache
|
||||
func checkCache(magnets: [Magnet]) async throws -> [Magnet] {
|
||||
private func checkCache(magnets: [Magnet]) async throws -> [Magnet] {
|
||||
var urlComponents = URLComponents(string: "\(baseApiUrl)/cache/check")!
|
||||
urlComponents.queryItems = magnets.map { URLQueryItem(name: "items[]", value: $0.hash) }
|
||||
guard let url = urlComponents.url else {
|
||||
|
|
@ -277,7 +278,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
// MARK: - Downloading
|
||||
|
||||
// Wrapper function to fetch a DDL link from the API
|
||||
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
// Store the item in PM cloud for later use
|
||||
try await createTransfer(magnet: magnet)
|
||||
|
||||
|
|
@ -290,7 +291,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func createTransfer(magnet: Magnet) async throws {
|
||||
private func createTransfer(magnet: Magnet) async throws {
|
||||
guard let magnetLink = magnet.link else {
|
||||
throw DebridError.FailedRequest(description: "The magnet link is invalid")
|
||||
}
|
||||
|
|
@ -309,7 +310,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
|
||||
// MARK: - Cloud methods
|
||||
|
||||
public func getUserDownloads() async throws {
|
||||
func getUserDownloads() async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/listall")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -325,7 +326,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func itemDetails(itemID: String) async throws -> ItemDetailsResponse {
|
||||
private 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 {
|
||||
|
|
@ -340,12 +341,12 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
return rawResponse
|
||||
}
|
||||
|
||||
public func checkUserDownloads(link: String) async throws -> String? {
|
||||
func checkUserDownloads(link: String) async throws -> String? {
|
||||
// Link is the cloud item ID
|
||||
try await itemDetails(itemID: link).link
|
||||
}
|
||||
|
||||
public func deleteDownload(downloadId: String) async throws {
|
||||
func deleteDownload(downloadId: 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")
|
||||
|
|
@ -359,7 +360,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// No user torrents for Premiumize
|
||||
public func getUserTorrents() async throws {}
|
||||
func getUserTorrents() async throws {}
|
||||
|
||||
public func deleteTorrent(torrentId: String?) async throws {}
|
||||
func deleteTorrent(torrentId: String?) async throws {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,20 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class RealDebrid: PollingDebridSource, ObservableObject {
|
||||
public let id = "RealDebrid"
|
||||
public let abbreviation = "RD"
|
||||
public let website = "https://real-debrid.com"
|
||||
public var authTask: Task<Void, Error>?
|
||||
class RealDebrid: PollingDebridSource, ObservableObject {
|
||||
let id = "RealDebrid"
|
||||
let abbreviation = "RD"
|
||||
let website = "https://real-debrid.com"
|
||||
var authTask: Task<Void, Error>?
|
||||
|
||||
@Published public var authProcessing: Bool = false
|
||||
@Published var authProcessing: Bool = false
|
||||
|
||||
// Check the manual token since getTokens() is async
|
||||
public var isLoggedIn: Bool {
|
||||
var isLoggedIn: Bool {
|
||||
FerriteKeychain.shared.get("RealDebrid.AccessToken") != nil
|
||||
}
|
||||
|
||||
public var manualToken: String? {
|
||||
var manualToken: String? {
|
||||
if UserDefaults.standard.bool(forKey: "RealDebrid.UseManualKey") {
|
||||
return FerriteKeychain.shared.get("RealDebrid.AccessToken")
|
||||
} else {
|
||||
|
|
@ -28,31 +28,31 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
public var cloudTTL: Double = 0.0
|
||||
@Published var IAValues: [DebridIA] = []
|
||||
@Published var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published var cloudTorrents: [DebridCloudTorrent] = []
|
||||
var cloudTTL: Double = 0.0
|
||||
|
||||
let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
||||
let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
||||
let openSourceClientId = "X245A4XAIBGVM"
|
||||
private let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
||||
private let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
||||
private let openSourceClientId = "X245A4XAIBGVM"
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
private let jsonDecoder = JSONDecoder()
|
||||
|
||||
@MainActor
|
||||
func setUserDefaultsValue(_ value: Any, forKey: String) {
|
||||
private func setUserDefaultsValue(_ value: Any, forKey: String) {
|
||||
UserDefaults.standard.set(value, forKey: forKey)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func removeUserDefaultsValue(forKey: String) {
|
||||
private func removeUserDefaultsValue(forKey: String) {
|
||||
UserDefaults.standard.removeObject(forKey: forKey)
|
||||
}
|
||||
|
||||
// MARK: - Auth
|
||||
|
||||
// Fetches the device code from RD
|
||||
public func getAuthUrl() async throws -> URL {
|
||||
func getAuthUrl() async throws -> URL {
|
||||
var urlComponents = URLComponents(string: "\(baseAuthUrl)/device/code")!
|
||||
urlComponents.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: openSourceClientId),
|
||||
|
|
@ -86,7 +86,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Fetches the user's client ID and secret
|
||||
public func getDeviceCredentials(deviceCode: String) async throws {
|
||||
func getDeviceCredentials(deviceCode: String) async throws {
|
||||
var urlComponents = URLComponents(string: "\(baseAuthUrl)/device/credentials")!
|
||||
urlComponents.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: openSourceClientId),
|
||||
|
|
@ -130,7 +130,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Fetch all tokens for the user and store in FerriteKeychain.shared
|
||||
public func getApiTokens(deviceCode: String) async throws {
|
||||
func getApiTokens(deviceCode: String) async throws {
|
||||
guard let clientId = UserDefaults.standard.string(forKey: "RealDebrid.ClientId") else {
|
||||
throw DebridError.EmptyData
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
await setUserDefaultsValue(accessTimestamp, forKey: "RealDebrid.AccessTokenStamp")
|
||||
}
|
||||
|
||||
public func getToken() async -> String? {
|
||||
func getToken() async -> String? {
|
||||
let accessTokenStamp = UserDefaults.standard.double(forKey: "RealDebrid.AccessTokenStamp")
|
||||
|
||||
if Date().timeIntervalSince1970 > accessTokenStamp {
|
||||
|
|
@ -183,7 +183,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
|
||||
// Adds a manual API key instead of web auth
|
||||
// Clear out existing refresh tokens and timestamps
|
||||
public func setApiKey(_ key: String) {
|
||||
func setApiKey(_ key: String) {
|
||||
FerriteKeychain.shared.set(key, forKey: "RealDebrid.AccessToken")
|
||||
FerriteKeychain.shared.delete("RealDebrid.RefreshToken")
|
||||
FerriteKeychain.shared.delete("RealDebrid.AccessTokenStamp")
|
||||
|
|
@ -192,7 +192,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Deletes tokens from device and RD's servers
|
||||
public func logout() async {
|
||||
func logout() async {
|
||||
FerriteKeychain.shared.delete("RealDebrid.RefreshToken")
|
||||
FerriteKeychain.shared.delete("RealDebrid.ClientSecret")
|
||||
await removeUserDefaultsValue(forKey: "RealDebrid.ClientId")
|
||||
|
|
@ -237,7 +237,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Instant availability
|
||||
|
||||
// Checks if the magnet is streamable on RD
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
func instantAvailability(magnets: [Magnet]) async throws {
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
let sendMagnets = magnets.filter { magnet in
|
||||
|
|
@ -328,7 +328,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Downloading
|
||||
|
||||
// Wrapper function to fetch a download link from the API
|
||||
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String {
|
||||
var selectedMagnetId = ""
|
||||
|
||||
do {
|
||||
|
|
@ -360,7 +360,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Adds a magnet link to the user's RD account
|
||||
public func addMagnet(magnet: Magnet) async throws -> String {
|
||||
func addMagnet(magnet: Magnet) async throws -> String {
|
||||
guard let magnetLink = magnet.link else {
|
||||
throw DebridError.FailedRequest(description: "The magnet link is invalid")
|
||||
}
|
||||
|
|
@ -381,7 +381,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Queues the magnet link for downloading
|
||||
public func selectFiles(debridID: String, fileIds: [Int]) async throws {
|
||||
func selectFiles(debridID: String, fileIds: [Int]) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/selectFiles/\(debridID)")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
|
@ -401,7 +401,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Gets the info of a torrent from a given ID
|
||||
public func torrentInfo(debridID: String, selectedFileId: Int?) async throws -> String {
|
||||
func torrentInfo(debridID: String, selectedFileId: Int?) async throws -> String {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -420,7 +420,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Downloads link from selectFiles for playback
|
||||
public func unrestrictLink(debridDownloadLink: String) async throws -> String {
|
||||
func unrestrictLink(debridDownloadLink: String) async throws -> String {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/unrestrict/link")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
|
@ -439,7 +439,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Cloud methods
|
||||
|
||||
// Gets the user's torrent library
|
||||
public func getUserTorrents() async throws {
|
||||
func getUserTorrents() async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -457,7 +457,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Deletes a torrent download from RD
|
||||
public func deleteTorrent(torrentId: String?) async throws {
|
||||
func deleteTorrent(torrentId: String?) async throws {
|
||||
let deleteId: String
|
||||
|
||||
if let torrentId {
|
||||
|
|
@ -480,7 +480,7 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Gets the user's downloads
|
||||
public func getUserDownloads() async throws {
|
||||
func getUserDownloads() async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
@ -491,11 +491,11 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
}
|
||||
|
||||
// Not used
|
||||
public func checkUserDownloads(link: String) -> String? {
|
||||
func checkUserDownloads(link: String) -> String? {
|
||||
nil
|
||||
}
|
||||
|
||||
public func deleteDownload(downloadId: String) async throws {
|
||||
func deleteDownload(downloadId: String) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads/delete/\(downloadId)")!)
|
||||
request.httpMethod = "DELETE"
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ import CoreData
|
|||
import Foundation
|
||||
|
||||
@objc(Bookmark)
|
||||
public class Bookmark: NSManagedObject {}
|
||||
class Bookmark: NSManagedObject {}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public extension Bookmark {
|
||||
extension Bookmark {
|
||||
@nonobjc class func fetchRequest() -> NSFetchRequest<Bookmark> {
|
||||
NSFetchRequest<Bookmark>(entityName: "Bookmark")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
public extension Color {
|
||||
extension Color {
|
||||
init(hex: String) {
|
||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
var int: UInt64 = 0
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import SwiftUI
|
|||
extension View {
|
||||
// Modifies properties of a view. Works the same way as a ViewModifier
|
||||
// From: https://github.com/SwiftUIX/SwiftUIX/blob/master/Sources/Intermodular/Extensions/SwiftUI/View%2B%2B.swift#L10
|
||||
public func modifyViewProp(_ body: (inout Self) -> Void) -> Self {
|
||||
func modifyViewProp(_ body: (inout Self) -> Void) -> Self {
|
||||
var result = self
|
||||
body(&result)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,20 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct ActionJson: Codable, Hashable, PluginJson {
|
||||
public let name: String
|
||||
public let version: Int16
|
||||
struct ActionJson: Codable, Hashable, PluginJson {
|
||||
let name: String
|
||||
let version: Int16
|
||||
let minVersion: String?
|
||||
let about: String?
|
||||
let website: String?
|
||||
let requires: [ActionRequirement]
|
||||
let deeplink: [DeeplinkActionJson]?
|
||||
public let author: String?
|
||||
public let listId: UUID?
|
||||
public let listName: String?
|
||||
public let tags: [PluginTagJson]?
|
||||
let author: String?
|
||||
let listId: UUID?
|
||||
let listName: String?
|
||||
let tags: [PluginTagJson]?
|
||||
|
||||
public init(name: String,
|
||||
init(name: String,
|
||||
version: Int16,
|
||||
minVersion: String?,
|
||||
about: String?,
|
||||
|
|
@ -45,7 +45,7 @@ public struct ActionJson: Codable, Hashable, PluginJson {
|
|||
self.tags = tags
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
name = try container.decode(String.self, forKey: .name)
|
||||
version = try container.decode(Int16.self, forKey: .version)
|
||||
|
|
@ -68,7 +68,7 @@ public struct ActionJson: Codable, Hashable, PluginJson {
|
|||
}
|
||||
}
|
||||
|
||||
public struct DeeplinkActionJson: Codable, Hashable {
|
||||
struct DeeplinkActionJson: Codable, Hashable {
|
||||
let os: [String]
|
||||
let scheme: String
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ public struct DeeplinkActionJson: Codable, Hashable {
|
|||
self.scheme = scheme
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let os = try? container.decode(String.self, forKey: .os) {
|
||||
|
|
@ -92,7 +92,7 @@ public struct DeeplinkActionJson: Codable, Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
public extension ActionJson {
|
||||
extension ActionJson {
|
||||
// Fetches all tags without optional requirement
|
||||
// Avoids the need for extra tag additions in DB
|
||||
func getTags() -> [PluginTagJson] {
|
||||
|
|
@ -100,7 +100,7 @@ public extension ActionJson {
|
|||
}
|
||||
}
|
||||
|
||||
public enum ActionRequirement: String, Codable {
|
||||
enum ActionRequirement: String, Codable {
|
||||
case magnet
|
||||
case debrid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public extension AllDebrid {
|
||||
extension AllDebrid {
|
||||
// MARK: - Generic AllDebrid response
|
||||
|
||||
// Uses a generic parametr for whatever underlying response is present
|
||||
|
|
@ -71,7 +71,7 @@ public extension AllDebrid {
|
|||
struct MagnetStatusResponse: Codable {
|
||||
let magnets: [MagnetStatusData]
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let data = try? container.decode(MagnetStatusData.self, forKey: .magnets) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
|
||||
// Version is optional until v1 is phased out
|
||||
public struct Backup: Codable {
|
||||
struct Backup: Codable {
|
||||
let version: Int?
|
||||
var bookmarks: [BookmarkJson]?
|
||||
var history: [HistoryJson]?
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
|
||||
// MARK: - Universal IA enum (IA = InstantAvailability)
|
||||
|
||||
public enum IAStatus: String, Codable, Hashable, Sendable, CaseIterable {
|
||||
enum IAStatus: String, Codable, Hashable, Sendable, CaseIterable {
|
||||
case full = "Cached"
|
||||
case partial = "Batch"
|
||||
case none = "Uncached"
|
||||
|
|
@ -18,7 +18,7 @@ public enum IAStatus: String, Codable, Hashable, Sendable, CaseIterable {
|
|||
|
||||
// MARK: - Enum for debrid differentiation. 0 is nil
|
||||
|
||||
public enum DebridType: Int, Codable, Hashable, CaseIterable {
|
||||
enum DebridType: Int, Codable, Hashable, CaseIterable {
|
||||
case realDebrid = 1
|
||||
case allDebrid = 2
|
||||
case premiumize = 3
|
||||
|
|
@ -47,7 +47,7 @@ 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 {
|
||||
struct Magnet: Codable, Hashable, Sendable {
|
||||
var hash: String?
|
||||
var link: String?
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct DebridIA: Hashable, Sendable {
|
||||
struct DebridIA: Hashable, Sendable {
|
||||
let magnet: Magnet
|
||||
let source: String
|
||||
let expiryTimeStamp: Double
|
||||
var files: [DebridIAFile]
|
||||
}
|
||||
|
||||
public struct DebridIAFile: Hashable, Sendable {
|
||||
struct DebridIAFile: Hashable, Sendable {
|
||||
let fileId: Int
|
||||
let name: String
|
||||
let streamUrlString: String?
|
||||
|
|
@ -28,14 +28,14 @@ public struct DebridIAFile: Hashable, Sendable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct DebridCloudDownload: Hashable, Sendable {
|
||||
struct DebridCloudDownload: Hashable, Sendable {
|
||||
let downloadId: String
|
||||
let source: String
|
||||
let fileName: String
|
||||
let link: String
|
||||
}
|
||||
|
||||
public struct DebridCloudTorrent: Hashable, Sendable {
|
||||
struct DebridCloudTorrent: Hashable, Sendable {
|
||||
let torrentId: String
|
||||
let source: String
|
||||
let fileName: String
|
||||
|
|
@ -44,7 +44,7 @@ public struct DebridCloudTorrent: Hashable, Sendable {
|
|||
let links: [String]
|
||||
}
|
||||
|
||||
public enum DebridError: Error {
|
||||
enum DebridError: Error {
|
||||
case InvalidUrl
|
||||
case InvalidPostBody
|
||||
case InvalidResponse
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public extension Github {
|
||||
extension Github {
|
||||
struct Release: Codable, Hashable, Sendable {
|
||||
let htmlUrl: String
|
||||
let tagName: String
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct PluginListJson: Codable {
|
||||
struct PluginListJson: Codable {
|
||||
let name: String
|
||||
let author: String
|
||||
var sources: [SourceJson]?
|
||||
|
|
@ -16,8 +16,8 @@ public struct PluginListJson: Codable {
|
|||
|
||||
// Color: Hex value
|
||||
public struct PluginTagJson: Codable, Hashable, Sendable {
|
||||
public let name: String
|
||||
public let colorHex: String?
|
||||
let name: String
|
||||
let colorHex: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public extension Premiumize {
|
||||
extension Premiumize {
|
||||
// MARK: - CacheCheckResponse
|
||||
|
||||
struct CacheCheckResponse: Codable {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public extension RealDebrid {
|
||||
extension RealDebrid {
|
||||
// MARK: - device code endpoint
|
||||
|
||||
struct DeviceCodeResponse: Codable, Sendable {
|
||||
|
|
@ -58,7 +58,7 @@ public extension RealDebrid {
|
|||
struct InstantAvailabilityResponse: Codable, Sendable {
|
||||
var data: InstantAvailabilityData?
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
if let data = try? container.decode(InstantAvailabilityData.self) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
|
||||
// A raw search result structure displayed on the UI
|
||||
public struct SearchResult: Codable, Hashable, Sendable {
|
||||
struct SearchResult: Codable, Hashable, Sendable {
|
||||
let title: String?
|
||||
let source: String
|
||||
let size: String?
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public enum ApiCredentialResponseType: String, Codable, Hashable, Sendable {
|
||||
enum ApiCredentialResponseType: String, Codable, Hashable, Sendable {
|
||||
case json
|
||||
case text
|
||||
}
|
||||
|
||||
public struct SourceJson: Codable, Hashable, Sendable, PluginJson {
|
||||
public let name: String
|
||||
public let version: Int16
|
||||
struct SourceJson: Codable, Hashable, Sendable, PluginJson {
|
||||
let name: String
|
||||
let version: Int16
|
||||
let minVersion: String?
|
||||
let about: String?
|
||||
let website: String?
|
||||
|
|
@ -25,33 +25,33 @@ public struct SourceJson: Codable, Hashable, Sendable, PluginJson {
|
|||
let jsonParser: SourceJsonParserJson?
|
||||
let rssParser: SourceRssParserJson?
|
||||
let htmlParser: SourceHtmlParserJson?
|
||||
public let author: String?
|
||||
public let listId: UUID?
|
||||
public let listName: String?
|
||||
public let tags: [PluginTagJson]?
|
||||
let author: String?
|
||||
let listId: UUID?
|
||||
let listName: String?
|
||||
let tags: [PluginTagJson]?
|
||||
}
|
||||
|
||||
public extension SourceJson {
|
||||
extension SourceJson {
|
||||
// Fetches all tags without optional requirement
|
||||
func getTags() -> [PluginTagJson] {
|
||||
tags ?? []
|
||||
}
|
||||
}
|
||||
|
||||
public enum SourcePreferredParser: Int16, CaseIterable, Sendable {
|
||||
enum SourcePreferredParser: Int16, CaseIterable, Sendable {
|
||||
// case none = 0
|
||||
case scraping = 1
|
||||
case rss = 2
|
||||
case siteApi = 3
|
||||
}
|
||||
|
||||
public struct SourceApiJson: Codable, Hashable, Sendable {
|
||||
struct SourceApiJson: Codable, Hashable, Sendable {
|
||||
let apiUrl: String?
|
||||
let clientId: SourceApiCredentialJson?
|
||||
let clientSecret: SourceApiCredentialJson?
|
||||
}
|
||||
|
||||
public struct SourceApiCredentialJson: Codable, Hashable, Sendable {
|
||||
struct SourceApiCredentialJson: Codable, Hashable, Sendable {
|
||||
let query: String?
|
||||
let value: String?
|
||||
let dynamic: Bool?
|
||||
|
|
@ -60,7 +60,7 @@ public struct SourceApiCredentialJson: Codable, Hashable, Sendable {
|
|||
let expiryLength: Double?
|
||||
}
|
||||
|
||||
public struct SourceJsonParserJson: Codable, Hashable, Sendable {
|
||||
struct SourceJsonParserJson: Codable, Hashable, Sendable {
|
||||
let searchUrl: String
|
||||
let request: SourceRequestJson?
|
||||
let results: String?
|
||||
|
|
@ -73,7 +73,7 @@ public struct SourceJsonParserJson: Codable, Hashable, Sendable {
|
|||
let sl: SourceSLJson?
|
||||
}
|
||||
|
||||
public struct SourceRssParserJson: Codable, Hashable, Sendable {
|
||||
struct SourceRssParserJson: Codable, Hashable, Sendable {
|
||||
let rssUrl: String?
|
||||
let searchUrl: String
|
||||
let request: SourceRequestJson?
|
||||
|
|
@ -86,7 +86,7 @@ public struct SourceRssParserJson: Codable, Hashable, Sendable {
|
|||
let sl: SourceSLJson?
|
||||
}
|
||||
|
||||
public struct SourceHtmlParserJson: Codable, Hashable, Sendable {
|
||||
struct SourceHtmlParserJson: Codable, Hashable, Sendable {
|
||||
let searchUrl: String?
|
||||
let request: SourceRequestJson?
|
||||
let rows: String
|
||||
|
|
@ -97,21 +97,21 @@ public struct SourceHtmlParserJson: Codable, Hashable, Sendable {
|
|||
let sl: SourceSLJson?
|
||||
}
|
||||
|
||||
public struct SourceComplexQueryJson: Codable, Hashable, Sendable {
|
||||
struct SourceComplexQueryJson: Codable, Hashable, Sendable {
|
||||
let query: String
|
||||
let discriminator: String?
|
||||
let attribute: String?
|
||||
let regex: String?
|
||||
}
|
||||
|
||||
public struct SourceMagnetJson: Codable, Hashable, Sendable {
|
||||
struct SourceMagnetJson: Codable, Hashable, Sendable {
|
||||
let query: String
|
||||
let attribute: String
|
||||
let regex: String?
|
||||
let externalLinkQuery: String?
|
||||
}
|
||||
|
||||
public struct SourceSLJson: Codable, Hashable, Sendable {
|
||||
struct SourceSLJson: Codable, Hashable, Sendable {
|
||||
let seeders: String?
|
||||
let leechers: String?
|
||||
let combined: String?
|
||||
|
|
@ -121,7 +121,7 @@ public struct SourceSLJson: Codable, Hashable, Sendable {
|
|||
let leecherRegex: String?
|
||||
}
|
||||
|
||||
public struct SourceRequestJson: Codable, Hashable, Sendable {
|
||||
struct SourceRequestJson: Codable, Hashable, Sendable {
|
||||
let method: String?
|
||||
let headers: [String: String]?
|
||||
let body: String?
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public protocol DebridSource: AnyObservableObject {
|
||||
protocol DebridSource: AnyObservableObject {
|
||||
// ID of the service
|
||||
// var id: DebridInfo { get }
|
||||
var id: String { get }
|
||||
|
|
@ -51,7 +51,7 @@ public protocol DebridSource: AnyObservableObject {
|
|||
func deleteTorrent(torrentId: String?) async throws
|
||||
}
|
||||
|
||||
public protocol PollingDebridSource: DebridSource {
|
||||
protocol PollingDebridSource: DebridSource {
|
||||
// Task reference for polling
|
||||
var authTask: Task<Void, Error>? { get set }
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ public protocol PollingDebridSource: DebridSource {
|
|||
func getAuthUrl() async throws -> URL
|
||||
}
|
||||
|
||||
public protocol OAuthDebridSource: DebridSource {
|
||||
protocol OAuthDebridSource: DebridSource {
|
||||
// Fetches the auth URL
|
||||
func getAuthUrl() throws -> URL
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public protocol Plugin: ObservableObject, NSManagedObject {
|
||||
protocol Plugin: ObservableObject, NSManagedObject {
|
||||
var id: UUID { get set }
|
||||
var listId: UUID? { get set }
|
||||
var name: String { get set }
|
||||
|
|
@ -27,7 +27,7 @@ extension Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol PluginJson: Hashable {
|
||||
protocol PluginJson: Hashable {
|
||||
var name: String { get }
|
||||
var version: Int16 { get }
|
||||
var author: String? { get }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Application {
|
||||
class Application {
|
||||
static let shared = Application()
|
||||
|
||||
// OS name for Plugins to read. Lowercase for ease of use
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ErasedObservableObject: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol AnyObservableObject: AnyObject {
|
||||
protocol AnyObservableObject: AnyObject {
|
||||
var objectWillChange: ObservableObjectPublisher { get }
|
||||
}
|
||||
|
||||
|
|
@ -59,14 +59,14 @@ public protocol AnyObservableObject: AnyObject {
|
|||
/// Not all injected objects need this property wrapper. See the example projects for examples each
|
||||
/// way.
|
||||
@propertyWrapper
|
||||
public struct Store<ObjectType> {
|
||||
struct Store<ObjectType> {
|
||||
/// The underlying object being stored.
|
||||
public let wrappedValue: ObjectType
|
||||
let wrappedValue: ObjectType
|
||||
|
||||
// See https://github.com/Tiny-Home-Consulting/Dependiject/issues/38
|
||||
fileprivate var _observableObject: ObservedObject<ErasedObservableObject>
|
||||
|
||||
@MainActor internal var observableObject: ErasedObservableObject {
|
||||
@MainActor var observableObject: ErasedObservableObject {
|
||||
_observableObject.wrappedValue
|
||||
}
|
||||
|
||||
|
|
@ -83,14 +83,14 @@ public struct Store<ObjectType> {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public var projectedValue: Wrapper {
|
||||
var projectedValue: Wrapper {
|
||||
Wrapper(self)
|
||||
}
|
||||
|
||||
/// Create a stored value on a custom scheduler.
|
||||
///
|
||||
/// Use this init to schedule updates on a specific scheduler other than `DispatchQueue.main`.
|
||||
public init<S: Scheduler>(wrappedValue: ObjectType,
|
||||
init<S: Scheduler>(wrappedValue: ObjectType,
|
||||
on scheduler: S,
|
||||
schedulerOptions: S.SchedulerOptions? = nil)
|
||||
{
|
||||
|
|
@ -112,7 +112,7 @@ public struct Store<ObjectType> {
|
|||
/// Create a stored value which publishes on the main thread.
|
||||
///
|
||||
/// To control when updates are published, see ``init(wrappedValue:on:schedulerOptions:)``.
|
||||
public init(wrappedValue: ObjectType) {
|
||||
init(wrappedValue: ObjectType) {
|
||||
self.init(wrappedValue: wrappedValue, on: DispatchQueue.main)
|
||||
}
|
||||
|
||||
|
|
@ -120,15 +120,15 @@ public struct Store<ObjectType> {
|
|||
/// [`ObservedObject.Wrapper`](https://developer.apple.com/documentation/swiftui/observedobject/wrapper)
|
||||
/// type.
|
||||
@dynamicMemberLookup
|
||||
public struct Wrapper {
|
||||
struct Wrapper {
|
||||
private var store: Store
|
||||
|
||||
internal init(_ store: Store<ObjectType>) {
|
||||
init(_ store: Store<ObjectType>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
/// Returns a binding to the resulting value of a given key path.
|
||||
public subscript<Subject>(
|
||||
subscript<Subject>(
|
||||
dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>
|
||||
) -> Binding<Subject> {
|
||||
Binding {
|
||||
|
|
@ -141,7 +141,7 @@ public struct Store<ObjectType> {
|
|||
}
|
||||
|
||||
extension Store: DynamicProperty {
|
||||
public nonisolated mutating func update() {
|
||||
nonisolated mutating func update() {
|
||||
_observableObject.update()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class BackupManager: ObservableObject {
|
||||
class BackupManager: ObservableObject {
|
||||
// Constant variable for backup versions
|
||||
let latestBackupVersion: Int = 2
|
||||
private let latestBackupVersion: Int = 2
|
||||
|
||||
var logManager: LoggingManager?
|
||||
|
||||
|
|
@ -21,17 +21,17 @@ public class BackupManager: ObservableObject {
|
|||
@Published var selectedBackupUrl: URL?
|
||||
|
||||
@MainActor
|
||||
func updateRestoreCompletedMessage(newString: String) {
|
||||
private func updateRestoreCompletedMessage(newString: String) {
|
||||
restoreCompletedMessage.append(newString)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func toggleRestoreCompletedAlert() {
|
||||
private func toggleRestoreCompletedAlert() {
|
||||
showRestoreCompletedAlert.toggle()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateBackupUrls(newUrl: URL) {
|
||||
private func updateBackupUrls(newUrl: URL) {
|
||||
backupUrls.append(newUrl)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public class DebridManager: ObservableObject {
|
||||
class DebridManager: ObservableObject {
|
||||
// Linked classes
|
||||
var logManager: LoggingManager?
|
||||
@Published var realDebrid: RealDebrid = .init()
|
||||
|
|
@ -40,7 +40,7 @@ public class DebridManager: ObservableObject {
|
|||
var selectedDebridFile: DebridIAFile?
|
||||
|
||||
// TODO: Figure out a way to remove this var
|
||||
var selectedOAuthDebridSource: OAuthDebridSource?
|
||||
private var selectedOAuthDebridSource: OAuthDebridSource?
|
||||
|
||||
@Published var filteredIAStatus: Set<IAStatus> = []
|
||||
|
||||
|
|
@ -48,17 +48,8 @@ public class DebridManager: ObservableObject {
|
|||
var downloadUrl: String = ""
|
||||
var authUrl: URL?
|
||||
|
||||
// RealDebrid auth variables
|
||||
var realDebridAuthProcessing: Bool = false
|
||||
|
||||
@Published var showDeleteAlert: Bool = false
|
||||
|
||||
// AllDebrid auth variables
|
||||
var allDebridAuthProcessing: Bool = false
|
||||
|
||||
// Premiumize auth variables
|
||||
var premiumizeAuthProcessing: Bool = false
|
||||
|
||||
init() {
|
||||
// Set the preferred service. Contains migration logic for earlier versions
|
||||
if let rawPreferredService = UserDefaults.standard.string(forKey: "Debrid.PreferredService") {
|
||||
|
|
@ -83,7 +74,7 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// TODO: Remove after v0.8.0
|
||||
// Function to migrate the preferred service to the new string ID format
|
||||
public func migratePreferredService(_ idInt: Int) -> String? {
|
||||
private func migratePreferredService(_ idInt: Int) -> String? {
|
||||
// Undo the EnabledDebrids key
|
||||
UserDefaults.standard.removeObject(forKey: "Debrid.EnabledArray")
|
||||
|
||||
|
|
@ -92,7 +83,7 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// Wrapper function to match error descriptions
|
||||
// Error can be suppressed to end user but must be printed in logs
|
||||
func sendDebridError(
|
||||
private func sendDebridError(
|
||||
_ error: Error,
|
||||
prefix: String,
|
||||
presentError: Bool = true,
|
||||
|
|
@ -119,20 +110,20 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Cleans all cached IA values in the event of a full IA refresh
|
||||
public func clearIAValues() {
|
||||
func clearIAValues() {
|
||||
for debridSource in debridSources {
|
||||
debridSource.IAValues = []
|
||||
}
|
||||
}
|
||||
|
||||
// Clears all selected files and items
|
||||
public func clearSelectedDebridItems() {
|
||||
func clearSelectedDebridItems() {
|
||||
selectedDebridItem = nil
|
||||
selectedDebridFile = nil
|
||||
}
|
||||
|
||||
// Common function to populate hashes for debrid services
|
||||
public func populateDebridIA(_ resultMagnets: [Magnet]) async {
|
||||
func populateDebridIA(_ resultMagnets: [Magnet]) async {
|
||||
for debridSource in debridSources {
|
||||
if !debridSource.isLoggedIn {
|
||||
continue
|
||||
|
|
@ -148,7 +139,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Common function to match a magnet hash with a provided debrid service
|
||||
public func matchMagnetHash(_ magnet: Magnet) -> IAStatus {
|
||||
func matchMagnetHash(_ magnet: Magnet) -> IAStatus {
|
||||
guard let magnetHash = magnet.hash else {
|
||||
return .none
|
||||
}
|
||||
|
|
@ -162,7 +153,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func selectDebridResult(magnet: Magnet) -> Bool {
|
||||
func selectDebridResult(magnet: Magnet) -> Bool {
|
||||
guard let magnetHash = magnet.hash else {
|
||||
logManager?.error("DebridManager: Could not find the torrent magnet hash")
|
||||
return false
|
||||
|
|
@ -184,7 +175,7 @@ public class DebridManager: ObservableObject {
|
|||
// MARK: - Authentication UI linked functions
|
||||
|
||||
// Common function to delegate what debrid service to authenticate with
|
||||
public func authenticateDebrid(_ debridSource: some DebridSource, apiKey: String?) async {
|
||||
func authenticateDebrid(_ debridSource: some DebridSource, apiKey: String?) async {
|
||||
defer {
|
||||
// Don't cancel processing if using OAuth
|
||||
if !(debridSource is OAuthDebridSource) {
|
||||
|
|
@ -253,7 +244,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Wrapper function to validate and present an auth URL to the user
|
||||
@discardableResult func validateAuthUrl(_ url: URL?, useAuthSession: Bool = false) -> Bool {
|
||||
@discardableResult private func validateAuthUrl(_ url: URL?, useAuthSession: Bool = false) -> Bool {
|
||||
guard let url else {
|
||||
logManager?.error("DebridManager: Authentication: Invalid URL created: \(String(describing: url))")
|
||||
return false
|
||||
|
|
@ -270,7 +261,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Currently handles Premiumize callback
|
||||
public func handleAuthCallback(url: URL?, error: Error?) async {
|
||||
func handleAuthCallback(url: URL?, error: Error?) async {
|
||||
defer {
|
||||
if enabledDebridCount == 1 {
|
||||
selectedDebridSource = selectedOAuthDebridSource
|
||||
|
|
@ -300,7 +291,7 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// MARK: - Logout UI functions
|
||||
|
||||
public func logout(_ debridSource: some DebridSource) async {
|
||||
func logout(_ debridSource: some DebridSource) async {
|
||||
await debridSource.logout()
|
||||
|
||||
if selectedDebridSource?.id == debridSource.id {
|
||||
|
|
@ -312,7 +303,7 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// Common function to delegate what debrid service to fetch from
|
||||
// Cloudinfo is used for any extra information provided by debrid cloud
|
||||
public func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
|
||||
func fetchDebridDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
|
||||
defer {
|
||||
currentDebridTask = nil
|
||||
logManager?.hideIndeterminateToast()
|
||||
|
|
@ -359,7 +350,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Wrapper to handle cloud fetching
|
||||
public func fetchDebridCloud(bypassTTL: Bool = false) async {
|
||||
func fetchDebridCloud(bypassTTL: Bool = false) async {
|
||||
guard let selectedSource = selectedDebridSource else {
|
||||
return
|
||||
}
|
||||
|
|
@ -381,7 +372,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func deleteCloudDownload(_ download: DebridCloudDownload) async {
|
||||
func deleteCloudDownload(_ download: DebridCloudDownload) async {
|
||||
guard let selectedSource = selectedDebridSource else {
|
||||
return
|
||||
}
|
||||
|
|
@ -395,7 +386,7 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func deleteCloudTorrent(_ torrent: DebridCloudTorrent) async {
|
||||
func deleteCloudTorrent(_ torrent: DebridCloudTorrent) async {
|
||||
guard let selectedSource = selectedDebridSource else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class LoggingManager: ObservableObject {
|
|||
|
||||
// TODO: Maybe append to a constant logfile?
|
||||
|
||||
public func info(_ message: String,
|
||||
func info(_ message: String,
|
||||
description: String? = nil)
|
||||
{
|
||||
let log = Log(
|
||||
|
|
@ -88,7 +88,7 @@ class LoggingManager: ObservableObject {
|
|||
print("LOG: \(log.toMessage())")
|
||||
}
|
||||
|
||||
public func warn(_ message: String,
|
||||
func warn(_ message: String,
|
||||
description: String? = nil)
|
||||
{
|
||||
let log = Log(
|
||||
|
|
@ -106,7 +106,7 @@ class LoggingManager: ObservableObject {
|
|||
print("LOG: \(log.toMessage())")
|
||||
}
|
||||
|
||||
public func error(_ message: String,
|
||||
func error(_ message: String,
|
||||
description: String? = nil,
|
||||
showToast: Bool = true)
|
||||
{
|
||||
|
|
@ -132,7 +132,7 @@ class LoggingManager: ObservableObject {
|
|||
|
||||
// MARK: - Indeterminate functions
|
||||
|
||||
public func updateIndeterminateToast(_ description: String, cancelAction: (() -> Void)?) {
|
||||
func updateIndeterminateToast(_ description: String, cancelAction: (() -> Void)?) {
|
||||
indeterminateToastDescription = description
|
||||
|
||||
if let cancelAction {
|
||||
|
|
@ -144,13 +144,13 @@ class LoggingManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func hideIndeterminateToast() {
|
||||
func hideIndeterminateToast() {
|
||||
showIndeterminateToast = false
|
||||
indeterminateToastDescription = ""
|
||||
indeterminateCancelAction = nil
|
||||
}
|
||||
|
||||
public func exportLogs() {
|
||||
func exportLogs() {
|
||||
logFormatter.dateFormat = "yyyy-MM-dd-HHmmss"
|
||||
let logFileName = "ferrite_session_\(logFormatter.string(from: Date())).txt"
|
||||
let logFolderPath = FileManager.default.appDirectory.appendingPathComponent("Logs")
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public class NavigationViewModel: ObservableObject {
|
||||
class NavigationViewModel: ObservableObject {
|
||||
var logManager: LoggingManager?
|
||||
|
||||
// Used between SearchResultsView and MagnetChoiceView
|
||||
public enum ChoiceSheetType: Identifiable {
|
||||
public var id: Int {
|
||||
enum ChoiceSheetType: Identifiable {
|
||||
var id: Int {
|
||||
hashValue
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ public class NavigationViewModel: ObservableObject {
|
|||
@Published var currentSortFilter: SortFilter?
|
||||
@Published var currentSortOrder: SortOrder = .forward
|
||||
|
||||
public func compareSearchResult(lhs: SearchResult, rhs: SearchResult) -> Bool {
|
||||
func compareSearchResult(lhs: SearchResult, rhs: SearchResult) -> Bool {
|
||||
switch currentSortFilter {
|
||||
case .leechers:
|
||||
guard let lhsLeechers = lhs.leechers, let rhsLeechers = rhs.leechers else {
|
||||
|
|
@ -97,7 +97,7 @@ public class NavigationViewModel: ObservableObject {
|
|||
|
||||
@Published var searchPrompt: String = "Search"
|
||||
@Published var lastSearchPromptIndex: Int = -1
|
||||
let searchBarTextArray: [String] = [
|
||||
private let searchBarTextArray: [String] = [
|
||||
"What's on your mind?",
|
||||
"Discover something interesting",
|
||||
"Find an engaging show",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Foundation
|
|||
import SwiftUI
|
||||
import Yams
|
||||
|
||||
public class PluginManager: ObservableObject {
|
||||
class PluginManager: ObservableObject {
|
||||
var logManager: LoggingManager?
|
||||
let kodi: Kodi = .init()
|
||||
|
||||
|
|
@ -25,18 +25,18 @@ public class PluginManager: ObservableObject {
|
|||
@Published var actionSuccessAlertMessage: String = ""
|
||||
|
||||
@MainActor
|
||||
func cleanAvailablePlugins() {
|
||||
private func cleanAvailablePlugins() {
|
||||
availableSources = []
|
||||
availableActions = []
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateAvailablePlugins(_ newPlugins: AvailablePlugins) {
|
||||
private func updateAvailablePlugins(_ newPlugins: AvailablePlugins) {
|
||||
availableSources += newPlugins.availableSources
|
||||
availableActions += newPlugins.availableActions
|
||||
}
|
||||
|
||||
public func fetchPluginsFromUrl() async {
|
||||
func fetchPluginsFromUrl() async {
|
||||
let pluginListRequest = PluginList.fetchRequest()
|
||||
guard let pluginLists = try? PersistenceController.shared.backgroundContext.fetch(pluginListRequest) else {
|
||||
await logManager?.error("PluginManager: No plugin lists found")
|
||||
|
|
@ -97,7 +97,7 @@ public class PluginManager: ObservableObject {
|
|||
await logManager?.info("Plugin list fetch finished")
|
||||
}
|
||||
|
||||
func fetchPluginList(pluginList: PluginList, url: URL) async throws -> AvailablePlugins? {
|
||||
private func fetchPluginList(pluginList: PluginList, url: URL) async throws -> AvailablePlugins? {
|
||||
var tempSources: [SourceJson] = []
|
||||
var tempActions: [ActionJson] = []
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Checks if a deeplink action is present and if there's a single action for the OS (or fallback)
|
||||
func getFilteredDeeplinks(_ deeplinks: [DeeplinkActionJson]) -> [DeeplinkActionJson]? {
|
||||
private func getFilteredDeeplinks(_ deeplinks: [DeeplinkActionJson]) -> [DeeplinkActionJson]? {
|
||||
let osArray = deeplinks.filter { deeplink in
|
||||
deeplink.os.contains(where: { $0.lowercased() == Application.shared.os.lowercased() })
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func fetchCastedPlugins<PJ: PluginJson>(_ forType: PJ.Type) -> [PJ] {
|
||||
private func fetchCastedPlugins<PJ: PluginJson>(_ forType: PJ.Type) -> [PJ] {
|
||||
switch String(describing: PJ.self) {
|
||||
case "SourceJson":
|
||||
return availableSources as? [PJ] ?? []
|
||||
|
|
@ -256,7 +256,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Checks if the current app version is supported by the source
|
||||
func checkAppVersion(minVersion: String?) -> Bool {
|
||||
private func checkAppVersion(minVersion: String?) -> Bool {
|
||||
// If there's no min version, assume that every version is supported
|
||||
guard let minVersion else {
|
||||
return true
|
||||
|
|
@ -266,7 +266,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Fetches sources using the background context
|
||||
public func fetchInstalledSources(searchResultsEmpty: Bool) -> [Source] {
|
||||
func fetchInstalledSources(searchResultsEmpty: Bool) -> [Source] {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
if !filteredInstalledSources.isEmpty, !searchResultsEmpty {
|
||||
|
|
@ -279,7 +279,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
public func runDefaultAction(urlString: String?, navModel: NavigationViewModel) {
|
||||
func runDefaultAction(urlString: String?, navModel: NavigationViewModel) {
|
||||
let context = PersistenceController.shared.backgroundContext
|
||||
|
||||
guard let urlString else {
|
||||
|
|
@ -332,7 +332,7 @@ public class PluginManager: ObservableObject {
|
|||
|
||||
// The iOS version of Ferrite only runs deeplink actions
|
||||
@MainActor
|
||||
public func runDeeplinkAction(_ action: Action, urlString: String?) {
|
||||
func runDeeplinkAction(_ action: Action, urlString: String?) {
|
||||
guard let deeplink = action.deeplink, let urlString else {
|
||||
actionErrorAlertMessage = "Could not run action: \(action.name) since there is no deeplink to execute. Contact the action dev!"
|
||||
showActionErrorAlert.toggle()
|
||||
|
|
@ -355,7 +355,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
public func sendToKodi(urlString: String?, server: KodiServer) async {
|
||||
func sendToKodi(urlString: String?, server: KodiServer) async {
|
||||
guard let urlString else {
|
||||
actionErrorAlertMessage = "Could not send URL to Kodi since there is no playback URL to send"
|
||||
showActionErrorAlert.toggle()
|
||||
|
|
@ -380,7 +380,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func installAction(actionJson: ActionJson?, doUpsert: Bool = false) async {
|
||||
func installAction(actionJson: ActionJson?, doUpsert: Bool = false) async {
|
||||
guard let actionJson else {
|
||||
await logManager?.error("Action addition: No action present. Contact the app dev!")
|
||||
return
|
||||
|
|
@ -448,7 +448,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func installSource(sourceJson: SourceJson?, doUpsert: Bool = false) async {
|
||||
func installSource(sourceJson: SourceJson?, doUpsert: Bool = false) async {
|
||||
guard let sourceJson else {
|
||||
await logManager?.error("Source addition: No source present. Contact the app dev!")
|
||||
return
|
||||
|
|
@ -535,7 +535,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func addSourceApi(newSource: Source, apiJson: SourceApiJson) {
|
||||
private func addSourceApi(newSource: Source, apiJson: SourceApiJson) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
let newSourceApi = SourceApi(context: backgroundContext)
|
||||
|
|
@ -571,7 +571,7 @@ public class PluginManager: ObservableObject {
|
|||
}
|
||||
|
||||
// TODO: Migrate parser addition to a common protocol
|
||||
func addJsonParser(newSource: Source, jsonParserJson: SourceJsonParserJson) {
|
||||
private func addJsonParser(newSource: Source, jsonParserJson: SourceJsonParserJson) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
let newSourceJsonParser = SourceJsonParser(context: backgroundContext)
|
||||
|
|
@ -646,7 +646,7 @@ public class PluginManager: ObservableObject {
|
|||
newSource.jsonParser = newSourceJsonParser
|
||||
}
|
||||
|
||||
func addRssParser(newSource: Source, rssParserJson: SourceRssParserJson) {
|
||||
private func addRssParser(newSource: Source, rssParserJson: SourceRssParserJson) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
let newSourceRssParser = SourceRssParser(context: backgroundContext)
|
||||
|
|
@ -725,7 +725,7 @@ public class PluginManager: ObservableObject {
|
|||
newSource.rssParser = newSourceRssParser
|
||||
}
|
||||
|
||||
func addHtmlParser(newSource: Source, htmlParserJson: SourceHtmlParserJson) {
|
||||
private func addHtmlParser(newSource: Source, htmlParserJson: SourceHtmlParserJson) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
let newSourceHtmlParser = SourceHtmlParser(context: backgroundContext)
|
||||
|
|
@ -795,7 +795,7 @@ public class PluginManager: ObservableObject {
|
|||
|
||||
// Adds a plugin list
|
||||
// Can move this to PersistenceController if needed
|
||||
public func addPluginList(_ urlString: String, isSheet: Bool = false, existingPluginList: PluginList? = nil) async throws {
|
||||
func addPluginList(_ urlString: String, isSheet: Bool = false, existingPluginList: PluginList? = nil) async throws {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
if urlString.isEmpty || !urlString.starts(with: "https://") && !urlString.starts(with: "http://") {
|
||||
|
|
|
|||
|
|
@ -27,18 +27,18 @@ class ScrapingViewModel: ObservableObject {
|
|||
|
||||
// Only add results with valid magnet hashes to the search results array
|
||||
@MainActor
|
||||
func updateSearchResults(newResults: [SearchResult]) {
|
||||
private func updateSearchResults(newResults: [SearchResult]) {
|
||||
searchResults += newResults
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func clearSearchResults() {
|
||||
private func clearSearchResults() {
|
||||
searchResults = []
|
||||
}
|
||||
|
||||
@Published var currentSourceNames: Set<String> = []
|
||||
@MainActor
|
||||
func updateCurrentSourceNames(_ newName: String) {
|
||||
private func updateCurrentSourceNames(_ newName: String) {
|
||||
currentSourceNames.insert(newName)
|
||||
logManager?.updateIndeterminateToast(
|
||||
"Loading \(currentSourceNames.joined(separator: ", "))",
|
||||
|
|
@ -47,7 +47,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
func removeCurrentSourceName(_ removedName: String) {
|
||||
private func removeCurrentSourceName(_ removedName: String) {
|
||||
currentSourceNames.remove(removedName)
|
||||
logManager?.updateIndeterminateToast(
|
||||
"Loading \(currentSourceNames.joined(separator: ", "))",
|
||||
|
|
@ -56,18 +56,18 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
func clearCurrentSourceNames() {
|
||||
private func clearCurrentSourceNames() {
|
||||
currentSourceNames = []
|
||||
logManager?.updateIndeterminateToast("Loading sources", cancelAction: nil)
|
||||
}
|
||||
|
||||
// Utility function to print source specific errors
|
||||
func sendSourceError(_ description: String) async {
|
||||
private func sendSourceError(_ description: String) async {
|
||||
await logManager?.error(description, showToast: false)
|
||||
}
|
||||
|
||||
// Substitutes the given string with an arbitrary parameter dictionary
|
||||
func substituteParams(_ input: String, with params: [String: String]) -> String {
|
||||
private func substituteParams(_ input: String, with params: [String: String]) -> String {
|
||||
let replaced = params.reduce(input) { result, param -> String in
|
||||
result.replacingOccurrences(of: "{\(param.key)}", with: param.value)
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// Cleans a SourceRequest's body and headers to be substituted
|
||||
func cleanRequest(request: SourceRequest, params: [String: String]) -> SourceRequest {
|
||||
private func cleanRequest(request: SourceRequest, params: [String: String]) -> SourceRequest {
|
||||
if let body = request.body {
|
||||
request.body = substituteParams(body, with: params)
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
return request
|
||||
}
|
||||
|
||||
public func scanSources(sources: [Source], searchText: String, debridManager: DebridManager) async {
|
||||
func scanSources(sources: [Source], searchText: String, debridManager: DebridManager) async {
|
||||
await logManager?.info("Started scanning sources for query \"\(searchText)\"")
|
||||
|
||||
if sources.isEmpty {
|
||||
|
|
@ -166,7 +166,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func executeParser(source: Source) async -> SearchRequestResult? {
|
||||
private func executeParser(source: Source) async -> SearchRequestResult? {
|
||||
guard let website = source.website else {
|
||||
await logManager?.error("Scraping: The base URL could not be found for source \(source.name)")
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// Checks the base URL for any website data then iterates through the fallback URLs
|
||||
func handleUrls(website: String, replacedSearchUrl: String?, fallbackUrls: [String]?, sourceName: String, requestParams: SourceRequest?) async -> Data? {
|
||||
private func handleUrls(website: String, replacedSearchUrl: String?, fallbackUrls: [String]?, sourceName: String, requestParams: SourceRequest?) async -> Data? {
|
||||
let fetchUrl = website + (replacedSearchUrl.map { $0 } ?? "")
|
||||
if let data = await fetchWebsiteData(urlString: fetchUrl, sourceName: sourceName, requestParams: requestParams) {
|
||||
return data
|
||||
|
|
@ -312,7 +312,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
return nil
|
||||
}
|
||||
|
||||
public func handleApiCredential(_ credential: SourceApiCredential,
|
||||
private func handleApiCredential(_ credential: SourceApiCredential,
|
||||
replacement: String,
|
||||
searchUrl: String,
|
||||
apiUrl: String?,
|
||||
|
|
@ -353,7 +353,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
return nil
|
||||
}
|
||||
|
||||
public func fetchApiCredential(urlString: String,
|
||||
private func fetchApiCredential(urlString: String,
|
||||
credential: SourceApiCredential,
|
||||
sourceName: String) async -> String?
|
||||
{
|
||||
|
|
@ -399,7 +399,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// Fetches the data for a URL
|
||||
public func fetchWebsiteData(urlString: String, sourceName: String, requestParams: SourceRequest?) async -> Data? {
|
||||
private func fetchWebsiteData(urlString: String, sourceName: String, requestParams: SourceRequest?) async -> Data? {
|
||||
guard let url = URL(string: urlString.trimmingCharacters(in: .whitespacesAndNewlines)) else {
|
||||
await sendSourceError("\(sourceName): Source doesn't contain a valid URL, contact the source dev!")
|
||||
|
||||
|
|
@ -446,7 +446,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func scrapeJson(source: Source, jsonData: Data) async -> SearchRequestResult? {
|
||||
private func scrapeJson(source: Source, jsonData: Data) async -> SearchRequestResult? {
|
||||
guard let jsonParser = source.jsonParser else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -521,7 +521,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// TODO: Add regex parsing for API
|
||||
public func parseJsonResult(_ result: JSON,
|
||||
private func parseJsonResult(_ result: JSON,
|
||||
jsonParser: SourceJsonParser,
|
||||
source: Source,
|
||||
existingSearchResult: SearchResult? = nil) -> SearchResult?
|
||||
|
|
@ -615,7 +615,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// RSS feed scraper
|
||||
public func scrapeRss(source: Source, rss: String) async -> SearchRequestResult? {
|
||||
private func scrapeRss(source: Source, rss: String) async -> SearchRequestResult? {
|
||||
guard let rssParser = source.rssParser else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -750,7 +750,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// Complex query parsing for RSS scraping
|
||||
func runRssComplexQuery(item: Element,
|
||||
private func runRssComplexQuery(item: Element,
|
||||
query: String,
|
||||
attribute: String,
|
||||
discriminator: String?,
|
||||
|
|
@ -783,7 +783,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// HTML scraper
|
||||
public func scrapeHtml(source: Source, website: String, html: String) async -> SearchRequestResult? {
|
||||
private func scrapeHtml(source: Source, website: String, html: String) async -> SearchRequestResult? {
|
||||
guard let htmlParser = source.htmlParser else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -955,7 +955,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
// Complex query parsing for HTML scraping
|
||||
func runHtmlComplexQuery(row: Element,
|
||||
private func runHtmlComplexQuery(row: Element,
|
||||
query: String,
|
||||
attribute: String,
|
||||
regexString: String?) throws -> String?
|
||||
|
|
@ -980,7 +980,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func runRegex(parsedValue: String, regexString: String) -> String? {
|
||||
private func runRegex(parsedValue: String, regexString: String) -> String? {
|
||||
// TODO: Maybe dynamically parse flags
|
||||
let replacedRegexString = regexString
|
||||
.replacingOccurrences(of: "{query}", with: cleanedSearchText)
|
||||
|
|
@ -1003,7 +1003,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func parseSizeString(sizeString: String) -> String? {
|
||||
private func parseSizeString(sizeString: String) -> String? {
|
||||
// Test if the string can be a full integer
|
||||
guard let size = Int(sizeString) else {
|
||||
return nil
|
||||
|
|
@ -1025,7 +1025,7 @@ class ScrapingViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func cleanApiCreds(api: SourceApi, sourceName: String) async {
|
||||
private func cleanApiCreds(api: SourceApi, sourceName: String) async {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
let hasCredentials = api.clientId != nil || api.clientSecret != nil
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ struct HybridSecureField: View {
|
|||
}
|
||||
|
||||
extension HybridSecureField {
|
||||
public func fieldDisabled(_ isFieldDisabled: Bool) -> Self {
|
||||
func fieldDisabled(_ isFieldDisabled: Bool) -> Self {
|
||||
modifyViewProp { $0.isFieldDisabled = isFieldDisabled }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
public extension View {
|
||||
extension View {
|
||||
// A dismissAction must be added in the parent view struct due to lifecycle issues
|
||||
func expandedSearchable(text: Binding<String>,
|
||||
isSearching: Binding<Bool>? = nil,
|
||||
|
|
|
|||
Loading…
Reference in a new issue