From ac66da899cb646bfcc744556ce93e08dc5fbeaaf Mon Sep 17 00:00:00 2001 From: cranci1 <100066266+cranci1@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:07:31 +0100 Subject: [PATCH] thank god man thank god --- Sora/Utils/Loaders/JSController.swift | 30 +++++-- .../EpisodeCell/EpisodeCell.swift | 7 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 88 +++++++++++++++++-- 3 files changed, 107 insertions(+), 18 deletions(-) diff --git a/Sora/Utils/Loaders/JSController.swift b/Sora/Utils/Loaders/JSController.swift index e15bf23..c54f029 100644 --- a/Sora/Utils/Loaders/JSController.swift +++ b/Sora/Utils/Loaders/JSController.swift @@ -70,9 +70,9 @@ class JSController: ObservableObject { }.resume() } - func fetchDetails(url: String, completion: @escaping ([MediaItem]) -> Void) { + func fetchDetails(url: String, completion: @escaping ([MediaItem], [EpisodeLink]) -> Void) { guard let url = URL(string: url) else { - completion([]) + completion([], []) return } @@ -81,31 +81,43 @@ class JSController: ObservableObject { if let error = error { print("Network error: \(error)") - DispatchQueue.main.async { completion([]) } + DispatchQueue.main.async { completion([], []) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { print("Failed to decode HTML") - DispatchQueue.main.async { completion([]) } + DispatchQueue.main.async { completion([], []) } return } + var resultItems: [MediaItem] = [] + var episodeLinks: [EpisodeLink] = [] + if let parseFunction = self.context.objectForKeyedSubscript("extractDetails"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { - let resultItems = results.map { item in + resultItems = results.map { item in MediaItem( description: item["description"] ?? "", aliases: item["aliases"] ?? "", airdate: item["airdate"] ?? "" ) } - DispatchQueue.main.async { - completion(resultItems) - } } else { print("Failed to parse results") - DispatchQueue.main.async { completion([]) } + } + + if let fetchEpisodesFunction = self.context.objectForKeyedSubscript("extractEpisodes"), + let episodesResult = fetchEpisodesFunction.call(withArguments: [html]).toArray() as? [[String: String]] { + for episodeData in episodesResult { + if let num = episodeData["number"], let link = episodeData["href"], let number = Int(num) { + episodeLinks.append(EpisodeLink(number: number, href: link)) + } + } + } + + DispatchQueue.main.async { + completion(resultItems, episodeLinks) } }.resume() } diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index a24ff25..d69506b 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -8,10 +8,15 @@ import SwiftUI import Kingfisher +struct EpisodeLink: Identifiable { + let id = UUID() + let number: Int + let href: String +} + struct EpisodeCell: View { let episode: String let episodeID: Int - let imageUrl: String let progress: Double let itemID: Int diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 4f03612..afad4eb 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -24,9 +24,8 @@ struct MediaInfoView: View { @State var aliases: String = "" @State var synopsis: String = "" @State var airdate: String = "" - @State var genres: [String] = [] - @State var episodes: [String] = [] - + @State var episodeLinks: [EpisodeLink] = [] + @State var itemID: Int? @State var isLoading: Bool = true @State var showFullSynopsis: Bool = false @@ -132,32 +131,57 @@ struct MediaInfoView: View { .frame(width: 20, height: 27) } } + + if !episodeLinks.isEmpty { + VStack(alignment: .leading, spacing: 10) { + Text("Episodes") + .font(.system(size: 18)) + .fontWeight(.bold) + + ForEach(episodeLinks.indices, id: \.self) { i in + let ep = episodeLinks[i] + let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(ep.href)") + let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(ep.href)") + let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0 + + EpisodeCell(episode: ep.href, episodeID: ep.number - 1, progress: progress, itemID: itemID ?? 0) + } + } + } } .padding() .navigationBarTitleDisplayMode(.inline) - .navigationBarTitle(title) + .navigationBarTitle("") .navigationViewStyle(StackNavigationViewStyle()) } } } .onAppear { - getDetails() + fetchDetails() + fetchItemID(byTitle: title) { result in + switch result { + case .success(let id): + itemID = id + case .failure(let error): + print("Failed to fetch Item ID: \(error)") + } + } } } - func getDetails() { + func fetchDetails() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { Task { do { let jsContent = try moduleManager.getModuleContent(module) jsController.loadScript(jsContent) - jsController.fetchDetails(url: href) { items in + jsController.fetchDetails(url: href) { items, episodes in if let item = items.first { - print("Fetched item: \(item)") self.synopsis = item.description self.aliases = item.aliases self.airdate = item.airdate } + self.episodeLinks = episodes self.isLoading = false } } catch { @@ -167,4 +191,52 @@ struct MediaInfoView: View { } } } + + private func fetchItemID(byTitle title: String, completion: @escaping (Result) -> Void) { + let query = """ + query { + Media(search: "\(title)", type: ANIME) { + id + } + } + """ + + guard let url = URL(string: "https://graphql.anilist.co") else { + completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))) + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let parameters: [String: Any] = ["query": query] + request.httpBody = try? JSONSerialization.data(withJSONObject: parameters) + + URLSession.custom.dataTask(with: request) { data, _, error in + if let error = error { + completion(.failure(error)) + return + } + + guard let data = data else { + completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"]))) + return + } + + do { + if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let data = json["data"] as? [String: Any], + let media = data["Media"] as? [String: Any], + let id = media["id"] as? Int { + completion(.success(id)) + } else { + let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]) + completion(.failure(error)) + } + } catch { + completion(.failure(error)) + } + }.resume() + } }