From 9af4984cc6f1caef1ee3ff8fb548cca4bb483f50 Mon Sep 17 00:00:00 2001 From: cranci1 <100066266+cranci1@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:07:44 +0100 Subject: [PATCH] added TMDB --- .../TMDB/HomePage/TMDB-Seasonal.swift | 57 +++++++++++++++++ .../TMDB/HomePage/TMDB-Trending.swift | 62 +++++++++++++++++++ .../TMDB/Struct/TMDBItem.swift | 61 ++++++++++++++++++ .../TMDB/Struct/TMDBRequest.swift | 41 ++++++++++++ Sora/Views/HomeView.swift | 29 ++++++--- .../SettingsViewTrackingServices.swift | 51 +++++++++++++++ Sora/Views/SettingsView/SettingsView.swift | 4 ++ Sulfur.xcodeproj/project.pbxproj | 44 +++++++++++++ 8 files changed, 342 insertions(+), 7 deletions(-) create mode 100644 Sora/Tracking Services/TMDB/HomePage/TMDB-Seasonal.swift create mode 100644 Sora/Tracking Services/TMDB/HomePage/TMDB-Trending.swift create mode 100644 Sora/Tracking Services/TMDB/Struct/TMDBItem.swift create mode 100644 Sora/Tracking Services/TMDB/Struct/TMDBRequest.swift create mode 100644 Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackingServices.swift diff --git a/Sora/Tracking Services/TMDB/HomePage/TMDB-Seasonal.swift b/Sora/Tracking Services/TMDB/HomePage/TMDB-Seasonal.swift new file mode 100644 index 0000000..08e9591 --- /dev/null +++ b/Sora/Tracking Services/TMDB/HomePage/TMDB-Seasonal.swift @@ -0,0 +1,57 @@ +// +// TMDB-Seasonal.swift +// Sulfur +// +// Created by Francesco on 05/03/25. +// + +import Foundation + +class TMDBSeasonal { + static func fetchTMDBSeasonal(completion: @escaping ([AniListItem]?) -> Void) { + Task { + do { + let url = URL(string: "https://api.themoviedb.org/3/movie/upcoming")! + var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! + components.queryItems = [ + URLQueryItem(name: "language", value: "en-US"), + URLQueryItem(name: "page", value: "1"), + ] + + var request = URLRequest(url: components.url!) + request.httpMethod = "GET" + request.timeoutInterval = 10 + request.allHTTPHeaderFields = [ + "accept": "application/json", + "Authorization": "Bearer \(TMBDRequest.decryptToken())" + ] + + let (data, _) = try await URLSession.custom.data(for: request) + let response = try JSONDecoder().decode(TMDBResponse.self, from: data) + + let anilistItems = response.results.map { item in + AniListItem( + id: item.id, + title: AniListTitle( + romaji: item.displayTitle, + english: item.originalTitle ?? item.originalName ?? item.displayTitle, + native: "" + ), + coverImage: AniListCoverImage( + large: item.posterURL + ) + ) + } + + DispatchQueue.main.async { + completion(anilistItems) + } + } catch { + DispatchQueue.main.async { + Logger.shared.log("Error fetching TMDB seasonal: \(error.localizedDescription)") + completion(nil) + } + } + } + } +} diff --git a/Sora/Tracking Services/TMDB/HomePage/TMDB-Trending.swift b/Sora/Tracking Services/TMDB/HomePage/TMDB-Trending.swift new file mode 100644 index 0000000..9024c7c --- /dev/null +++ b/Sora/Tracking Services/TMDB/HomePage/TMDB-Trending.swift @@ -0,0 +1,62 @@ +// +// TMDB-Trending.swift +// Sulfur +// +// Created by Francesco on 05/03/25. +// + +import Foundation + +class TMBDTrending { + static func fetchTMDBTrending(completion: @escaping ([AniListItem]?) -> Void) { + Task { + do { + let items = try await fetchTrendingItems() + + let anilistItems = items.map { item in + AniListItem( + id: item.id, + title: AniListTitle( + romaji: item.displayTitle, + english: item.originalTitle ?? item.originalName ?? item.displayTitle, + native: "" + ), + coverImage: AniListCoverImage( + large: item.posterURL + ) + ) + } + + DispatchQueue.main.async { + completion(anilistItems) + } + } catch { + DispatchQueue.main.async { + Logger.shared.log("Error fetching TMDB trending: \(error.localizedDescription)") + completion(nil) + } + } + } + } + + private static func fetchTrendingItems() async throws -> [TMDBItem] { + let url = URL(string: "https://api.themoviedb.org/3/trending/all/day")! + var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! + let queryItems: [URLQueryItem] = [ + URLQueryItem(name: "language", value: "en-US") + ] + components.queryItems = queryItems + + var request = URLRequest(url: components.url!) + request.httpMethod = "GET" + request.timeoutInterval = 10 + request.allHTTPHeaderFields = [ + "accept": "application/json", + "Authorization": "Bearer \(TMBDRequest.decryptToken())" + ] + + let (data, _) = try await URLSession.custom.data(for: request) + let response = try JSONDecoder().decode(TMDBResponse.self, from: data) + return response.results + } +} diff --git a/Sora/Tracking Services/TMDB/Struct/TMDBItem.swift b/Sora/Tracking Services/TMDB/Struct/TMDBItem.swift new file mode 100644 index 0000000..38d5998 --- /dev/null +++ b/Sora/Tracking Services/TMDB/Struct/TMDBItem.swift @@ -0,0 +1,61 @@ +// +// TMDBItem.swift +// Sulfur +// +// Created by Francesco on 05/03/25. +// + +import Foundation + +struct TMDBItem: Codable { + let id: Int + let mediaType: String? + + let title: String? + let originalTitle: String? + let releaseDate: String? + + let name: String? + let originalName: String? + let firstAirDate: String? + + let posterPath: String? + let backdropPath: String? + let overview: String + let voteAverage: Double? + + enum CodingKeys: String, CodingKey { + case id, overview + case mediaType = "media_type" + case title, name + case originalTitle = "original_title" + case originalName = "original_name" + case posterPath = "poster_path" + case backdropPath = "backdrop_path" + case releaseDate = "release_date" + case firstAirDate = "first_air_date" + case voteAverage = "vote_average" + } + + var displayTitle: String { + return title ?? name ?? "Unknown Title" + } + + var posterURL: String { + if let path = posterPath { + return "https://image.tmdb.org/t/p/w500\(path)" + } + return "" + } + + var backdropURL: String { + if let path = backdropPath { + return "https://image.tmdb.org/t/p/original\(path)" + } + return "" + } + + var displayDate: String { + return releaseDate ?? firstAirDate ?? "" + } +} diff --git a/Sora/Tracking Services/TMDB/Struct/TMDBRequest.swift b/Sora/Tracking Services/TMDB/Struct/TMDBRequest.swift new file mode 100644 index 0000000..aba029d --- /dev/null +++ b/Sora/Tracking Services/TMDB/Struct/TMDBRequest.swift @@ -0,0 +1,41 @@ +// +// TMDBRequest.swift +// Sulfur +// +// Created by Francesco on 05/03/25. +// + +import Foundation + +struct TMDBResponse: Codable { + let results: [TMDBItem] + let page: Int + let totalPages: Int + let totalResults: Int + + enum CodingKeys: String, CodingKey { + case results, page + case totalPages = "total_pages" + case totalResults = "total_results" + } +} + +class TMBDRequest { + static let encodedTokenParts = [ + "XZXlKaGJHY2lPaUpJVXpJMU5pSjk=", + "XZXlKaGRXUWlPaUkzTXpoaU5HVmtaREJoTVRVMlkyTXhNalprWXpSaE5HSTRZV1ZoTkdGallTSXNJbTVpWmlJNk1UYzBNVEUzTXpjd01pNDNPRGN3TURJc0luTjFZaUk2SWpZM1l6Z3pNMk0yWkRjME1UbGpaR1prT0RabE1tUmtaaUlzSW5OamIzQmxjeUk2V3lKaGNHbGZjbVZoWkNKZExDSjJaWEp6YVc5dUlqb3hmUT09", + "XR2ZlN0YtOENXSlhnT052MzRtZzNqSFhmTDZCeGJqLWhBWWY5ZllpOUNrRQ==" + ] + + static func decryptToken() -> String { + let decodedParts = encodedTokenParts.map { part -> String in + let cleanPart = String(part.dropFirst(1)) + guard let data = Data(base64Encoded: cleanPart) else { + return "" + } + return String(data: data, encoding: .utf8) ?? "" + } + + return decodedParts.joined() + } +} diff --git a/Sora/Views/HomeView.swift b/Sora/Views/HomeView.swift index d3792d5..7d17fff 100644 --- a/Sora/Views/HomeView.swift +++ b/Sora/Views/HomeView.swift @@ -9,6 +9,7 @@ import SwiftUI import Kingfisher struct HomeView: View { + @AppStorage("trackingService") private var tracingService: String = "AniList" @State private var aniListItems: [AniListItem] = [] @State private var trendingItems: [AniListItem] = [] @State private var continueWatchingItems: [ContinueWatchingItem] = [] @@ -257,14 +258,28 @@ struct HomeView: View { } .onAppear { continueWatchingItems = ContinueWatchingManager.shared.fetchItems() - AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in - if let items = items { - aniListItems = items + if tracingService == "TMDB" { + TMDBSeasonal.fetchTMDBSeasonal { items in + if let items = items { + aniListItems = items + } } - } - AnilistServiceTrendingAnime().fetchTrendingAnime { items in - if let items = items { - trendingItems = items + + TMBDTrending.fetchTMDBTrending { items in + if let items = items { + trendingItems = items + } + } + } else { + AnilistServiceSeasonalAnime().fetchSeasonalAnime { items in + if let items = items { + aniListItems = items + } + } + AnilistServiceTrendingAnime().fetchTrendingAnime { items in + if let items = items { + trendingItems = items + } } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackingServices.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackingServices.swift new file mode 100644 index 0000000..1625986 --- /dev/null +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackingServices.swift @@ -0,0 +1,51 @@ +// +// SettingsViewTrackingServices.swift +// Sulfur +// +// Created by Francesco on 05/03/25. +// + +import SwiftUI +import Kingfisher + +struct SettingsViewTrackingServices: View { + @AppStorage("trackingService") private var trackingService: String = "AniList" + @EnvironmentObject var settings: Settings + + var body: some View { + Form { + Section(header: Text("Tracking Service")) { + HStack { + Text("Service") + Spacer() + Menu { + Button(action: { trackingService = "AniList" }) { + HStack { + KFImage(URL(string: "https://avatars.githubusercontent.com/u/18018524?s=280&v=4")) + .resizable() + .frame(width: 20, height: 20) + Text("AniList") + } + } + Button(action: { trackingService = "TMDB" }) { + HStack { + KFImage(URL(string: "https://pbs.twimg.com/profile_images/1243623122089041920/gVZIvphd_400x400.jpg")) + .resizable() + .frame(width: 20, height: 20) + Text("TMDB") + } + } + } label: { + HStack { + KFImage(URL(string: trackingService == "TMDB" ? "https://pbs.twimg.com/profile_images/1243623122089041920/gVZIvphd_400x400.jpg" : "https://avatars.githubusercontent.com/u/18018524?s=280&v=4")) + .resizable() + .frame(width: 20, height: 20) + Text(trackingService) + } + } + } + } + } + .navigationTitle("Tracking Service") + } +} diff --git a/Sora/Views/SettingsView/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift index 1bd3698..e6295f2 100644 --- a/Sora/Views/SettingsView/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -21,6 +21,9 @@ struct SettingsView: View { NavigationLink(destination: SettingsViewModule()) { Text("Modules") } + NavigationLink(destination: SettingsViewTrackingServices()) { + Text("Tracking Services") + } } Section(header: Text("Info")) { @@ -73,6 +76,7 @@ struct SettingsView: View { } } } + Section(footer: Text("Running Sora 0.2.1")) {} } .navigationTitle("Settings") } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 8cb30fb..1d4d498 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -16,6 +16,11 @@ 131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */; }; 1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA62D758CEA00FC6689 /* Analytics.swift */; }; 1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */; }; + 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4C2D786C93007E289F /* TMDB-Seasonal.swift */; }; + 1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */; }; + 1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF512D7871B7007E289F /* TMDBItem.swift */; }; + 1334FF542D787217007E289F /* TMDBRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF532D787217007E289F /* TMDBRequest.swift */; }; + 1334FF562D7872E9007E289F /* SettingsViewTrackingServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */; }; 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6D2D2BE2500075467E /* SoraApp.swift */; }; 133D7C702D2BE2500075467E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C6F2D2BE2500075467E /* ContentView.swift */; }; 133D7C722D2BE2520075467E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 133D7C712D2BE2520075467E /* Assets.xcassets */; }; @@ -67,6 +72,11 @@ 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewGeneral.swift; sourceTree = ""; }; 1327FBA62D758CEA00FC6689 /* Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Model.swift"; sourceTree = ""; }; + 1334FF4C2D786C93007E289F /* TMDB-Seasonal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TMDB-Seasonal.swift"; sourceTree = ""; }; + 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TMDB-Trending.swift"; sourceTree = ""; }; + 1334FF512D7871B7007E289F /* TMDBItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBItem.swift; sourceTree = ""; }; + 1334FF532D787217007E289F /* TMDBRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TMDBRequest.swift; sourceTree = ""; }; + 1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTrackingServices.swift; sourceTree = ""; }; 133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; }; 133D7C6D2D2BE2500075467E /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = ""; }; 133D7C6F2D2BE2500075467E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -123,6 +133,7 @@ 13103E802D589D6C000F0673 /* Tracking Services */ = { isa = PBXGroup; children = ( + 1334FF4A2D786C6D007E289F /* TMDB */, 13103E812D589D77000F0673 /* AniList */, ); path = "Tracking Services"; @@ -173,6 +184,33 @@ path = Analytics; sourceTree = ""; }; + 1334FF4A2D786C6D007E289F /* TMDB */ = { + isa = PBXGroup; + children = ( + 1334FF502D7871A4007E289F /* Struct */, + 1334FF4B2D786C81007E289F /* HomePage */, + ); + path = TMDB; + sourceTree = ""; + }; + 1334FF4B2D786C81007E289F /* HomePage */ = { + isa = PBXGroup; + children = ( + 1334FF4C2D786C93007E289F /* TMDB-Seasonal.swift */, + 1334FF4E2D786C9E007E289F /* TMDB-Trending.swift */, + ); + path = HomePage; + sourceTree = ""; + }; + 1334FF502D7871A4007E289F /* Struct */ = { + isa = PBXGroup; + children = ( + 1334FF512D7871B7007E289F /* TMDBItem.swift */, + 1334FF532D787217007E289F /* TMDBRequest.swift */, + ); + path = Struct; + sourceTree = ""; + }; 133D7C612D2BE2500075467E = { isa = PBXGroup; children = ( @@ -243,6 +281,7 @@ 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */, 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */, 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */, + 1334FF552D7872E9007E289F /* SettingsViewTrackingServices.swift */, ); path = SettingsSubViews; sourceTree = ""; @@ -475,14 +514,18 @@ 13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */, 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, + 1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */, 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */, + 1334FF542D787217007E289F /* TMDBRequest.swift in Sources */, 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */, 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */, 133D7C932D2BE2640075467E /* Modules.swift in Sources */, + 1334FF562D7872E9007E289F /* SettingsViewTrackingServices.swift in Sources */, 136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */, 133D7C702D2BE2500075467E /* ContentView.swift in Sources */, + 1334FF522D7871B7007E289F /* TMDBItem.swift in Sources */, 13D99CF72D4E73C300250A86 /* ModuleAdditionSettingsView.swift in Sources */, 13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */, 13CBA0882D60F19C00EFE70A /* VTTSubtitlesLoader.swift in Sources */, @@ -507,6 +550,7 @@ 133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */, 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */, 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */, + 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */, 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */, 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */, 13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */,