plenty of things (fr)
Some checks are pending
Build and Release / Build IPA (push) Waiting to run
Build and Release / Build Mac Catalyst (push) Waiting to run

This commit is contained in:
cranci1 2025-06-11 11:14:21 +02:00
parent efe05e9a04
commit 75c9d6bf07
8 changed files with 127 additions and 79 deletions

View file

@ -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"

View file

@ -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")
}
}
}

View file

@ -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")
}
}
}

View file

@ -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()

View file

@ -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

View file

@ -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

View 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
}
}

View file

@ -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 */,