mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-14 05:20:25 +00:00
This commit is contained in:
parent
748cc4e999
commit
59b5f84077
3 changed files with 201 additions and 0 deletions
|
|
@ -0,0 +1,194 @@
|
|||
//
|
||||
// SettingsViewTrackers.swift
|
||||
// Sora
|
||||
//
|
||||
// Created by Francesco on 23/03/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Security
|
||||
import Kingfisher
|
||||
|
||||
struct SettingsViewTrackers: View {
|
||||
@State private var status: String = "You are not logged in"
|
||||
@State private var isLoggedIn: Bool = false
|
||||
@State private var username: String = ""
|
||||
@State private var isLoading: Bool = false
|
||||
@State private var profileColor: Color = .primary
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("AniList"), footer: Text("Sora and cranci1 are not affiliated with AniList in any way.")) {
|
||||
HStack() {
|
||||
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png"))
|
||||
.resizable()
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(Rectangle())
|
||||
Text("AniList.co")
|
||||
.font(.title2)
|
||||
}
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
} else {
|
||||
if isLoggedIn {
|
||||
HStack(spacing: 0) {
|
||||
Text("Logged in as ")
|
||||
Text(username)
|
||||
.foregroundColor(profileColor)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
} else {
|
||||
Text(status)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
Button(isLoggedIn ? "Log Out from AniList.co" : "Log In with AniList.co") {
|
||||
if isLoggedIn {
|
||||
logout()
|
||||
} else {
|
||||
login()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Trackers")
|
||||
.onAppear {
|
||||
updateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func login() {
|
||||
status = "Starting authentication..."
|
||||
AniListLogin.authenticate()
|
||||
}
|
||||
|
||||
func logout() {
|
||||
removeTokenFromKeychain()
|
||||
status = "You are not logged in"
|
||||
isLoggedIn = false
|
||||
username = ""
|
||||
profileColor = .primary
|
||||
}
|
||||
|
||||
func updateStatus() {
|
||||
if let token = getTokenFromKeychain() {
|
||||
isLoggedIn = true
|
||||
fetchUserInfo(token: token)
|
||||
} else {
|
||||
isLoggedIn = false
|
||||
status = "You are not logged in"
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUserInfo(token: String) {
|
||||
isLoading = true
|
||||
let userInfoURL = URL(string: "https://graphql.anilist.co")!
|
||||
var request = URLRequest(url: userInfoURL)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let query = """
|
||||
{
|
||||
Viewer {
|
||||
id
|
||||
name
|
||||
options {
|
||||
profileColor
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
let body: [String: Any] = ["query": query]
|
||||
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
|
||||
} catch {
|
||||
status = "Failed to serialize request"
|
||||
Logger.shared.log("Failed to serialize request", type: "Error")
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
DispatchQueue.main.async {
|
||||
isLoading = false
|
||||
if let error = error {
|
||||
status = "Error: \(error.localizedDescription)"
|
||||
Logger.shared.log("Error: \(error.localizedDescription)", type: "Error")
|
||||
return
|
||||
}
|
||||
guard let data = data else {
|
||||
status = "No data received"
|
||||
Logger.shared.log("No data received", type: "Error")
|
||||
return
|
||||
}
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
let dataDict = json["data"] as? [String: Any],
|
||||
let viewer = dataDict["Viewer"] as? [String: Any],
|
||||
let name = viewer["name"] as? String,
|
||||
let options = viewer["options"] as? [String: Any],
|
||||
let colorName = options["profileColor"] as? String {
|
||||
|
||||
username = name
|
||||
profileColor = colorFromName(colorName)
|
||||
status = "Logged in as \(name)"
|
||||
} else {
|
||||
status = "Unexpected response format!"
|
||||
Logger.shared.log("Unexpected response format!", type: "Error")
|
||||
}
|
||||
} catch {
|
||||
status = "Failed to parse response: \(error.localizedDescription)"
|
||||
Logger.shared.log("Failed to parse response: \(error.localizedDescription)", type: "Error")
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func colorFromName(_ name: String) -> Color {
|
||||
switch name.lowercased() {
|
||||
case "blue":
|
||||
return .blue
|
||||
case "purple":
|
||||
return .purple
|
||||
case "green":
|
||||
return .green
|
||||
case "orange":
|
||||
return .orange
|
||||
case "red":
|
||||
return .red
|
||||
case "pink":
|
||||
return .pink
|
||||
case "gray":
|
||||
return .gray
|
||||
default:
|
||||
return .accentColor
|
||||
}
|
||||
}
|
||||
|
||||
func getTokenFromKeychain() -> String? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: "me.cranci.sora.AniListToken",
|
||||
kSecAttrAccount as String: "AniListAccessToken",
|
||||
kSecReturnData as String: kCFBooleanTrue!,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne
|
||||
]
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
guard status == errSecSuccess, let tokenData = item as? Data else {
|
||||
return nil
|
||||
}
|
||||
return String(data: tokenData, encoding: .utf8)
|
||||
}
|
||||
|
||||
func removeTokenFromKeychain() {
|
||||
let deleteQuery: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: "me.cranci.sora.AniListToken",
|
||||
kSecAttrAccount as String: "AniListAccessToken"
|
||||
]
|
||||
SecItemDelete(deleteQuery as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,9 @@ struct SettingsView: View {
|
|||
NavigationLink(destination: SettingsViewModule()) {
|
||||
Text("Modules")
|
||||
}
|
||||
NavigationLink(destination: SettingsViewTrackers()) {
|
||||
Text("Trackers")
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Info")) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468B2D900939008CBC03 /* Anilist-Login.swift */; };
|
||||
13DB468E2D90093A008CBC03 /* Anilist-Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468C2D90093A008CBC03 /* Anilist-Token.swift */; };
|
||||
13DB46902D900A38008CBC03 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468F2D900A38008CBC03 /* URL.swift */; };
|
||||
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */; };
|
||||
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */; };
|
||||
13DB7CC62D7DC7D2004371D3 /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 13DB7CC52D7DC7D2004371D3 /* FFmpeg-iOS-Lame */; };
|
||||
13DB7CEC2D7DED5D004371D3 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */; };
|
||||
|
|
@ -108,6 +109,7 @@
|
|||
13DB468B2D900939008CBC03 /* Anilist-Login.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Login.swift"; sourceTree = "<group>"; };
|
||||
13DB468C2D90093A008CBC03 /* Anilist-Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Token.swift"; sourceTree = "<group>"; };
|
||||
13DB468F2D900A38008CBC03 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||
13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTrackers.swift; sourceTree = "<group>"; };
|
||||
13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSettingsManager.swift; sourceTree = "<group>"; };
|
||||
13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||
13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
|
@ -274,6 +276,7 @@
|
|||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
||||
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */,
|
||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
||||
13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */,
|
||||
);
|
||||
path = SettingsSubViews;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -561,6 +564,7 @@
|
|||
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */,
|
||||
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
||||
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
||||
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */,
|
||||
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue