mirror of
https://github.com/cranci1/Sora.git
synced 2026-01-11 20:10:24 +00:00
plenty of things (fr)
This commit is contained in:
parent
efe05e9a04
commit
75c9d6bf07
8 changed files with 127 additions and 79 deletions
|
|
@ -89,11 +89,7 @@ struct SoraApp: App {
|
|||
}
|
||||
}
|
||||
.onOpenURL { url in
|
||||
if let params = url.queryParameters, params["code"] != nil {
|
||||
Self.handleRedirect(url: url)
|
||||
} else {
|
||||
handleURL(url)
|
||||
}
|
||||
handleURL(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,38 +138,8 @@ struct SoraApp: App {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
static func handleRedirect(url: URL) {
|
||||
guard let params = url.queryParameters,
|
||||
let code = params["code"] else {
|
||||
Logger.shared.log("Failed to extract authorization code")
|
||||
return
|
||||
}
|
||||
|
||||
switch url.host {
|
||||
case "anilist":
|
||||
AniListToken.exchangeAuthorizationCodeForToken(code: code) { success in
|
||||
if success {
|
||||
Logger.shared.log("AniList token exchange successful")
|
||||
} else {
|
||||
Logger.shared.log("AniList token exchange failed", type: "Error")
|
||||
}
|
||||
}
|
||||
|
||||
case "trakt":
|
||||
TraktToken.exchangeAuthorizationCodeForToken(code: code) { success in
|
||||
if success {
|
||||
Logger.shared.log("Trakt token exchange successful")
|
||||
} else {
|
||||
Logger.shared.log("Trakt token exchange failed", type: "Error")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
Logger.shared.log("Unknown authentication service", type: "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppInfo: NSObject {
|
||||
@objc func getBundleIdentifier() -> String {
|
||||
return Bundle.main.bundleIdentifier ?? "me.cranci.sulfur"
|
||||
|
|
|
|||
|
|
@ -16,19 +16,28 @@ class AniListLogin {
|
|||
static func authenticate() {
|
||||
let urlString = "\(authorizationEndpoint)?client_id=\(clientID)&redirect_uri=\(redirectURI)&response_type=code"
|
||||
guard let url = URL(string: urlString) else {
|
||||
Logger.shared.log("Invalid authorization URL", type: "Error")
|
||||
return
|
||||
}
|
||||
|
||||
if UIApplication.shared.canOpenURL(url) {
|
||||
UIApplication.shared.open(url, options: [:]) { success in
|
||||
if success {
|
||||
Logger.shared.log("Safari opened successfully", type: "Debug")
|
||||
WebAuthenticationManager.shared.authenticate(url: url, callbackScheme: "sora") { result in
|
||||
switch result {
|
||||
case .success(let callbackURL):
|
||||
if let params = callbackURL.queryParameters,
|
||||
let code = params["code"] {
|
||||
AniListToken.exchangeAuthorizationCodeForToken(code: code) { success in
|
||||
if success {
|
||||
Logger.shared.log("AniList token exchange successful", type: "Debug")
|
||||
} else {
|
||||
Logger.shared.log("AniList token exchange failed", type: "Error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Failed to open Safari", type: "Error")
|
||||
Logger.shared.log("No authorization code in callback URL", type: "Error")
|
||||
}
|
||||
case .failure(let error):
|
||||
Logger.shared.log("Authentication failed: \(error.localizedDescription)", type: "Error")
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Cannot open URL", type: "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,28 @@ class TraktLogin {
|
|||
static func authenticate() {
|
||||
let urlString = "\(authorizationEndpoint)?client_id=\(clientID)&redirect_uri=\(redirectURI)&response_type=code"
|
||||
guard let url = URL(string: urlString) else {
|
||||
Logger.shared.log("Invalid authorization URL", type: "Error")
|
||||
return
|
||||
}
|
||||
|
||||
if UIApplication.shared.canOpenURL(url) {
|
||||
UIApplication.shared.open(url, options: [:]) { success in
|
||||
if success {
|
||||
Logger.shared.log("Safari opened successfully", type: "Debug")
|
||||
WebAuthenticationManager.shared.authenticate(url: url, callbackScheme: "sora") { result in
|
||||
switch result {
|
||||
case .success(let callbackURL):
|
||||
if let params = callbackURL.queryParameters,
|
||||
let code = params["code"] {
|
||||
TraktToken.exchangeAuthorizationCodeForToken(code: code) { success in
|
||||
if success {
|
||||
Logger.shared.log("Trakt token exchange successful", type: "Debug")
|
||||
} else {
|
||||
Logger.shared.log("Trakt token exchange failed", type: "Error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Failed to open Safari", type: "Error")
|
||||
Logger.shared.log("No authorization code in callback URL", type: "Error")
|
||||
}
|
||||
case .failure(let error):
|
||||
Logger.shared.log("Authentication failed: \(error.localizedDescription)", type: "Error")
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Cannot open URL", type: "Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,18 +30,7 @@ class TraktMutation {
|
|||
return token
|
||||
}
|
||||
|
||||
enum ExternalIDType {
|
||||
case tmdb(Int)
|
||||
|
||||
var dictionary: [String: Any] {
|
||||
switch self {
|
||||
case .tmdb(let id):
|
||||
return ["tmdb": id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func markAsWatched(type: String, externalID: ExternalIDType, episodeNumber: Int? = nil, seasonNumber: Int? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func markAsWatched(type: String, tmdbID: Int, episodeNumber: Int? = nil, seasonNumber: Int? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let sendTraktUpdates = UserDefaults.standard.object(forKey: "sendTraktUpdates") as? Bool,
|
||||
sendTraktUpdates == false {
|
||||
return
|
||||
|
|
@ -49,10 +38,12 @@ class TraktMutation {
|
|||
|
||||
guard let userToken = getTokenFromKeychain() else {
|
||||
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Access token not found"])))
|
||||
Logger.shared.log("Trakt Access token not found", type: "Error")
|
||||
return
|
||||
}
|
||||
|
||||
let endpoint = "/sync/history"
|
||||
let watchedAt = ISO8601DateFormatter().string(from: Date())
|
||||
let body: [String: Any]
|
||||
|
||||
switch type {
|
||||
|
|
@ -60,7 +51,8 @@ class TraktMutation {
|
|||
body = [
|
||||
"movies": [
|
||||
[
|
||||
"ids": externalID.dictionary
|
||||
"ids": ["tmdb": tmdbID],
|
||||
"watched_at": watchedAt
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
@ -74,12 +66,15 @@ class TraktMutation {
|
|||
body = [
|
||||
"shows": [
|
||||
[
|
||||
"ids": externalID.dictionary,
|
||||
"ids": ["tmdb": tmdbID],
|
||||
"seasons": [
|
||||
[
|
||||
"number": season,
|
||||
"episodes": [
|
||||
["number": episode]
|
||||
[
|
||||
"number": episode,
|
||||
"watched_at": watchedAt
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
@ -94,13 +89,13 @@ class TraktMutation {
|
|||
|
||||
var request = URLRequest(url: apiURL.appendingPathComponent(endpoint))
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(userToken)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(userToken)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("2", forHTTPHeaderField: "trakt-api-version")
|
||||
request.setValue(TraktToken.clientID, forHTTPHeaderField: "trakt-api-key")
|
||||
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [.prettyPrinted])
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
|
|
@ -112,15 +107,26 @@ class TraktMutation {
|
|||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
(200...299).contains(httpResponse.statusCode) else {
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
|
||||
completion(.failure(NSError(domain: "", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "Unexpected response or status code"])))
|
||||
return
|
||||
}
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
completion(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "No HTTP response"])))
|
||||
return
|
||||
}
|
||||
|
||||
Logger.shared.log("Successfully updated watch status on Trakt", type: "Debug")
|
||||
completion(.success(()))
|
||||
if (200...299).contains(httpResponse.statusCode) {
|
||||
if let data = data, let responseString = String(data: data, encoding: .utf8) {
|
||||
Logger.shared.log("Trakt API Response: \(responseString)", type: "Debug")
|
||||
}
|
||||
Logger.shared.log("Successfully updated watch status on Trakt", type: "Debug")
|
||||
completion(.success(()))
|
||||
} else {
|
||||
var errorMessage = "Unexpected status code: \(httpResponse.statusCode)"
|
||||
if let data = data,
|
||||
let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let error = errorJson["error"] as? String {
|
||||
errorMessage = error
|
||||
}
|
||||
completion(.failure(NSError(domain: "", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorMessage])))
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
|
|
|||
|
|
@ -1656,7 +1656,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
let traktMutation = TraktMutation()
|
||||
|
||||
if self.isMovie {
|
||||
traktMutation.markAsWatched(type: "movie", externalID: .tmdb(tmdbId)) { result in
|
||||
traktMutation.markAsWatched(type: "movie", tmdbID: tmdbId) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
Logger.shared.log("Successfully updated Trakt progress for movie", type: "General")
|
||||
|
|
@ -1667,7 +1667,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
|||
} else {
|
||||
traktMutation.markAsWatched(
|
||||
type: "episode",
|
||||
externalID: .tmdb(tmdbId),
|
||||
tmdbID: tmdbId,
|
||||
episodeNumber: self.episodeNumber,
|
||||
seasonNumber: self.seasonNumber
|
||||
) { result in
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ class VideoPlayerViewController: UIViewController {
|
|||
let traktMutation = TraktMutation()
|
||||
|
||||
if self.isMovie {
|
||||
traktMutation.markAsWatched(type: "movie", externalID: .tmdb(tmdbId)) { result in
|
||||
traktMutation.markAsWatched(type: "movie", tmdbID: tmdbId) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
Logger.shared.log("Successfully updated Trakt progress for movie", type: "General")
|
||||
|
|
@ -230,7 +230,7 @@ class VideoPlayerViewController: UIViewController {
|
|||
} else {
|
||||
traktMutation.markAsWatched(
|
||||
type: "episode",
|
||||
externalID: .tmdb(tmdbId),
|
||||
tmdbID: tmdbId,
|
||||
episodeNumber: self.episodeNumber,
|
||||
seasonNumber: self.seasonNumber
|
||||
) { result in
|
||||
|
|
|
|||
45
Sora/Utils/WebAuthentication/WebAuthenticationManager.swift
Normal file
45
Sora/Utils/WebAuthentication/WebAuthenticationManager.swift
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// WebAuthenticationManager.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 11/06/25.
|
||||
//
|
||||
|
||||
import AuthenticationServices
|
||||
|
||||
class WebAuthenticationManager {
|
||||
static let shared = WebAuthenticationManager()
|
||||
private var webAuthSession: ASWebAuthenticationSession?
|
||||
|
||||
func authenticate(url: URL, callbackScheme: String, completion: @escaping (Result<URL, Error>) -> Void) {
|
||||
webAuthSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackScheme) { callbackURL, error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
if let callbackURL = callbackURL {
|
||||
completion(.success(callbackURL))
|
||||
} else {
|
||||
completion(.failure(NSError(domain: "WebAuthenticationManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "No callback URL received"])))
|
||||
}
|
||||
}
|
||||
|
||||
webAuthSession?.presentationContextProvider = WebAuthenticationPresentationContext.shared
|
||||
webAuthSession?.prefersEphemeralWebBrowserSession = true
|
||||
webAuthSession?.start()
|
||||
}
|
||||
}
|
||||
|
||||
class WebAuthenticationPresentationContext: NSObject, ASWebAuthenticationPresentationContextProviding {
|
||||
static let shared = WebAuthenticationPresentationContext()
|
||||
|
||||
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first else {
|
||||
fatalError("No window found")
|
||||
}
|
||||
|
||||
return window
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
04F08EDF2DE10C1D006B29D9 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EDE2DE10C1A006B29D9 /* TabBar.swift */; };
|
||||
04F08EE22DE10C40006B29D9 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EE12DE10C27006B29D9 /* TabItem.swift */; };
|
||||
04F08EE42DE10D6F006B29D9 /* AllBookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F08EE32DE10D6B006B29D9 /* AllBookmarks.swift */; };
|
||||
130326B62DF979AC00AEF610 /* WebAuthenticationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130326B52DF979AC00AEF610 /* WebAuthenticationManager.swift */; };
|
||||
130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */; };
|
||||
13103E8B2D58E028000F0673 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8A2D58E028000F0673 /* View.swift */; };
|
||||
13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8D2D58E04A000F0673 /* SkeletonCell.swift */; };
|
||||
|
|
@ -114,6 +115,7 @@
|
|||
04F08EDE2DE10C1A006B29D9 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
|
||||
04F08EE12DE10C27006B29D9 /* TabItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = "<group>"; };
|
||||
04F08EE32DE10D6B006B29D9 /* AllBookmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllBookmarks.swift; sourceTree = "<group>"; };
|
||||
130326B52DF979AC00AEF610 /* WebAuthenticationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAuthenticationManager.swift; sourceTree = "<group>"; };
|
||||
130C6BF82D53A4C200DC1432 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = "<group>"; };
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = "<group>"; };
|
||||
13103E8A2D58E028000F0673 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -260,6 +262,15 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
130326B42DF979A300AEF610 /* WebAuthentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
130326B52DF979AC00AEF610 /* WebAuthenticationManager.swift */,
|
||||
);
|
||||
name = WebAuthentication;
|
||||
path = Sora/Utils/WebAuthentication;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
13103E802D589D6C000F0673 /* Tracking Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -378,6 +389,7 @@
|
|||
133D7C852D2BE2640075467E /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
130326B42DF979A300AEF610 /* WebAuthentication */,
|
||||
0457C5962DE7712A000AFBD9 /* ViewModifiers */,
|
||||
04F08EE02DE10C22006B29D9 /* Models */,
|
||||
04F08EDD2DE10C05006B29D9 /* TabBar */,
|
||||
|
|
@ -699,6 +711,7 @@
|
|||
1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */,
|
||||
136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */,
|
||||
13DB46902D900A38008CBC03 /* URL.swift in Sources */,
|
||||
130326B62DF979AC00AEF610 /* WebAuthenticationManager.swift in Sources */,
|
||||
04F08EE42DE10D6F006B29D9 /* AllBookmarks.swift in Sources */,
|
||||
138FE1D02DECA00D00936D81 /* TMDB-FetchID.swift in Sources */,
|
||||
130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */,
|
||||
|
|
|
|||
Loading…
Reference in a new issue