import Foundation import Siesta import SwiftUI final class AccountValidator: Service { let app: Binding let url: String let account: Account! var formObjectID: Binding var isValid: Binding var isValidated: Binding var isValidating: Binding var error: Binding? private var appsToValidateInstance = VideosApp.allCases init( app: Binding, url: String, account: Account? = nil, id: Binding, isValid: Binding, isValidated: Binding, isValidating: Binding, error: Binding? = nil ) { self.app = app self.url = url self.account = account formObjectID = id self.isValid = isValid self.isValidated = isValidated self.isValidating = isValidating self.error = error super.init(baseURL: url) configure() } func configure() { configure { $0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"]) } configure("/login", requestMethods: [.post]) { $0.headers["Content-Type"] = "application/json" } } func instanceValidationResource(_ app: VideosApp) -> Resource { switch app { case .invidious: return resource("/api/v1/videos/dQw4w9WgXcQ") case .piped: return resource("/streams/dQw4w9WgXcQ") } } func validateInstance() { reset() guard let app = appsToValidateInstance.popLast() else { return } tryValidatingUsing(app) } func tryValidatingUsing(_ app: VideosApp) { instanceValidationResource(app) .load() .onSuccess { response in guard self.url == self.formObjectID.wrappedValue else { return } guard !response.json.isEmpty else { guard let app = self.appsToValidateInstance.popLast() else { self.isValid.wrappedValue = false self.isValidated.wrappedValue = true self.isValidating.wrappedValue = false return } self.tryValidatingUsing(app) return } let json = response.json.dictionaryValue let author = app == .invidious ? json["author"] : json["uploader"] if author == "Rick Astley" { self.app.wrappedValue = app self.isValid.wrappedValue = true self.error?.wrappedValue = nil } else { self.isValid.wrappedValue = false } self.isValidated.wrappedValue = true self.isValidating.wrappedValue = false } .onFailure { error in guard self.url == self.formObjectID.wrappedValue else { return } if self.appsToValidateInstance.isEmpty { self.isValidating.wrappedValue = false self.isValidated.wrappedValue = true self.isValid.wrappedValue = false self.error?.wrappedValue = error.userMessage } else { guard let app = self.appsToValidateInstance.popLast() else { return } self.tryValidatingUsing(app) } } } func validateAccount() { reset() guard let request = accountRequest else { isValid.wrappedValue = false isValidated.wrappedValue = true isValidating.wrappedValue = false return } request.onSuccess { response in guard self.account!.username == self.formObjectID.wrappedValue else { return } switch self.app.wrappedValue { case .invidious: self.isValid.wrappedValue = true case .piped: let error = response.json.dictionaryValue["error"]?.string let token = response.json.dictionaryValue["token"]?.string self.isValid.wrappedValue = error?.isEmpty ?? !(token?.isEmpty ?? true) self.error!.wrappedValue = error default: return } } .onFailure { _ in guard self.account!.username == self.formObjectID.wrappedValue else { return } self.isValid.wrappedValue = false } .onCompletion { _ in self.isValidated.wrappedValue = true self.isValidating.wrappedValue = false } } var accountRequest: Request? { switch app.wrappedValue { case .invidious: guard let password = account.password else { return nil } return login.request(.post, urlEncoded: ["email": account.username, "password": password]) case .piped: return login.request(.post, json: ["username": account.username, "password": account.password]) default: return nil } } func reset() { appsToValidateInstance = VideosApp.allCases app.wrappedValue = nil isValid.wrappedValue = false isValidated.wrappedValue = false isValidating.wrappedValue = false error?.wrappedValue = nil } var login: Resource { resource("/login") } var videoResourceBasePath: String { app.wrappedValue == .invidious ? "/api/v1/videos" : "/streams" } var neverGonnaGiveYouUp: Resource { resource("\(videoResourceBasePath)/dQw4w9WgXcQ") } }