does even work yay
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run

This commit is contained in:
cranci1 2025-01-07 16:53:36 +01:00
parent fdbb3e1edc
commit 0d9ff05bb6
6 changed files with 352 additions and 86 deletions

View file

@ -21,6 +21,8 @@
133D7C932D2BE2640075467E /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C892D2BE2640075467E /* Modules.swift */; }; 133D7C932D2BE2640075467E /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C892D2BE2640075467E /* Modules.swift */; };
133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; }; 133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; };
133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; }; 133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; };
138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; };
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -38,6 +40,8 @@
133D7C872D2BE2640075467E /* URLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; }; 133D7C872D2BE2640075467E /* URLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
133D7C892D2BE2640075467E /* Modules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; }; 133D7C892D2BE2640075467E /* Modules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
133D7C8B2D2BE2640075467E /* JSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; }; 133D7C8B2D2BE2640075467E /* JSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; };
138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = "<group>"; };
138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -105,6 +109,7 @@
133D7C7F2D2BE2630075467E /* MediaInfoView */ = { 133D7C7F2D2BE2630075467E /* MediaInfoView */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
138AA1B52D2D66EC0021F9DF /* EpisodeCell */,
133D7C802D2BE2630075467E /* MediaInfoView.swift */, 133D7C802D2BE2630075467E /* MediaInfoView.swift */,
); );
path = MediaInfoView; path = MediaInfoView;
@ -152,6 +157,15 @@
path = Loaders; path = Loaders;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
138AA1B52D2D66EC0021F9DF /* EpisodeCell */ = {
isa = PBXGroup;
children = (
138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */,
138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */,
);
path = EpisodeCell;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -233,12 +247,14 @@
133D7C702D2BE2500075467E /* ContentView.swift in Sources */, 133D7C702D2BE2500075467E /* ContentView.swift in Sources */,
133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */, 133D7C8F2D2BE2640075467E /* MediaInfoView.swift in Sources */,
133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */, 133D7C8D2D2BE2640075467E /* HomeView.swift in Sources */,
138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */,
133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */, 133D7C8C2D2BE2640075467E /* SearchView.swift in Sources */,
133D7C942D2BE2640075467E /* JSController.swift in Sources */, 133D7C942D2BE2640075467E /* JSController.swift in Sources */,
133D7C922D2BE2640075467E /* URLSession.swift in Sources */, 133D7C922D2BE2640075467E /* URLSession.swift in Sources */,
133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */, 133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */,
133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */, 133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */,
133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */, 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */,
138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View file

@ -8,10 +8,40 @@
import Foundation import Foundation
extension URLSession { extension URLSession {
static let userAgents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.92",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.128",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.3; rv:123.0) Gecko/20100101 Firefox/123.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:122.0) Gecko/20100101 Firefox/122.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0",
"Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0",
"Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.105 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPad; CPU OS 17_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Android 14; Mobile; rv:123.0) Gecko/123.0 Firefox/123.0",
"Mozilla/5.0 (Android 13; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0"
]
static let randomUserAgent: String = {
userAgents.randomElement() ?? userAgents[0]
}()
static let custom: URLSession = { static let custom: URLSession = {
let configuration = URLSessionConfiguration.default let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = [ configuration.httpAdditionalHeaders = [
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" "User-Agent": randomUserAgent
] ]
return URLSession(configuration: configuration) return URLSession(configuration: configuration)
}() }()

View file

@ -36,7 +36,7 @@ class JSController: ObservableObject {
return return
} }
URLSession.custom.dataTask(with: url) { [weak self] data, response, error in URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return } guard let self = self else { return }
if let error = error { if let error = error {
@ -69,4 +69,44 @@ class JSController: ObservableObject {
} }
}.resume() }.resume()
} }
func fetchDetails(url: String, completion: @escaping ([MediaItem]) -> Void) {
guard let url = URL(string: url) else {
completion([])
return
}
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
guard let self = self else { return }
if let error = error {
print("Network error: \(error)")
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([]) }
return
}
if let parseFunction = self.context.objectForKeyedSubscript("extractDetails"),
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
let 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([]) }
}
}.resume()
}
} }

View file

@ -0,0 +1,36 @@
//
// CircularProgressBar.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import SwiftUI
struct CircularProgressBar: View {
var progress: Double
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 5.0)
.opacity(0.3)
.foregroundColor(Color.accentColor)
Circle()
.trim(from: 0.0, to: CGFloat(min(progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.accentColor)
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear, value: progress)
if progress >= 0.90 {
Image(systemName: "checkmark")
.font(.system(size: 12))
} else {
Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0))
.font(.system(size: 12))
}
}
}
}

View file

@ -0,0 +1,119 @@
//
// EpisodeCell.swift
// Sora
//
// Created by Francesco on 18/12/24.
//
import SwiftUI
import Kingfisher
struct EpisodeCell: View {
let episode: String
let episodeID: Int
let imageUrl: String
let progress: Double
let itemID: Int
@State private var episodeTitle: String = ""
@State private var episodeImageUrl: String = ""
@State private var isLoading: Bool = true
var body: some View {
HStack {
ZStack {
KFImage(URL(string: episodeImageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : episodeImageUrl))
.resizable()
.aspectRatio(16/9, contentMode: .fill)
.frame(width: 100, height: 56)
.cornerRadius(8)
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
VStack(alignment: .leading) {
Text("Episode \(episodeID + 1)")
.font(.system(size: 15))
if !episodeTitle.isEmpty {
Text(episodeTitle)
.font(.system(size: 13))
.foregroundColor(.secondary)
}
}
Spacer()
CircularProgressBar(progress: progress)
.frame(width: 40, height: 40)
}
.onAppear {
fetchEpisodeDetails()
}
}
func fetchEpisodeDetails() {
let cacheKey = "episodeDetails_\(itemID)_\(episodeID)"
if let cachedData = UserDefaults.standard.data(forKey: cacheKey) {
parseEpisodeDetails(data: cachedData)
return
}
guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else {
isLoading = false
return
}
URLSession.custom.dataTask(with: url) { data, _, error in
if let error = error {
print("Failed to fetch episode details: \(error)")
DispatchQueue.main.async {
self.isLoading = false
}
return
}
guard let data = data else {
print("No data received")
DispatchQueue.main.async {
self.isLoading = false
}
return
}
UserDefaults.standard.set(data, forKey: cacheKey)
self.parseEpisodeDetails(data: data)
}.resume()
}
func parseEpisodeDetails(data: Data) {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
guard let json = jsonObject as? [String: Any],
let episodes = json["episodes"] as? [String: Any],
let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any],
let title = episodeDetails["title"] as? [String: String],
let image = episodeDetails["image"] as? String else {
print("Invalid response format")
DispatchQueue.main.async {
self.isLoading = false
}
return
}
DispatchQueue.main.async {
self.episodeTitle = title["en"] ?? ""
self.episodeImageUrl = image
self.isLoading = false
}
} catch {
print("Failed to parse JSON: \(error)")
DispatchQueue.main.async {
self.isLoading = false
}
}
}
}

View file

@ -8,6 +8,13 @@
import SwiftUI import SwiftUI
import Kingfisher import Kingfisher
struct MediaItem: Identifiable {
let id = UUID()
let description: String
let aliases: String
let airdate: String
}
struct MediaInfoView: View { struct MediaInfoView: View {
let title: String let title: String
let imageUrl: String let imageUrl: String
@ -25,8 +32,14 @@ struct MediaInfoView: View {
@AppStorage("externalPlayer") private var externalPlayer: String = "Default" @AppStorage("externalPlayer") private var externalPlayer: String = "Default"
@ObservedObject var jsController = JSController()
var body: some View { var body: some View {
Group { Group {
if isLoading {
ProgressView()
.padding()
} else {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack(alignment: .top, spacing: 10) { HStack(alignment: .top, spacing: 10) {
@ -126,4 +139,16 @@ struct MediaInfoView: View {
} }
} }
} }
.onAppear {
jsController.fetchDetails(url: href) { items in
if let item = items.first {
print("Fetched item: \(item)")
self.synopsis = item.description
self.aliases = item.aliases
self.airdate = item.airdate
}
self.isLoading = false
}
}
}
} }