mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
Media Info view
This commit is contained in:
parent
d519fc6110
commit
ee75542b2d
7 changed files with 131 additions and 12 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
1329D5CB2D298199008AEDA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CA2D298199008AEDA2 /* Assets.xcassets */; };
|
1329D5CB2D298199008AEDA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CA2D298199008AEDA2 /* Assets.xcassets */; };
|
||||||
1329D5CE2D298199008AEDA2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */; };
|
1329D5CE2D298199008AEDA2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */; };
|
||||||
133D7C1B2D2ADC430075467E /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C1A2D2ADC430075467E /* URLSession.swift */; };
|
133D7C1B2D2ADC430075467E /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C1A2D2ADC430075467E /* URLSession.swift */; };
|
||||||
|
133D7C1E2D2ADF110075467E /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C1D2D2ADF110075467E /* MediaInfoView.swift */; };
|
||||||
13AEE6192D2A75110096D953 /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE6182D2A75110096D953 /* Modules.swift */; };
|
13AEE6192D2A75110096D953 /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE6182D2A75110096D953 /* Modules.swift */; };
|
||||||
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61A2D2A78050096D953 /* SettingsView.swift */; };
|
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61A2D2A78050096D953 /* SettingsView.swift */; };
|
||||||
13AEE61D2D2A78160096D953 /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61C2D2A78160096D953 /* JSController.swift */; };
|
13AEE61D2D2A78160096D953 /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61C2D2A78160096D953 /* JSController.swift */; };
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
1329D5DA2D29821B008AEDA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
1329D5DA2D29821B008AEDA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
133D7C1A2D2ADC430075467E /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
|
133D7C1A2D2ADC430075467E /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
|
||||||
|
133D7C1D2D2ADF110075467E /* MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoView.swift; sourceTree = "<group>"; };
|
||||||
13AEE6182D2A75110096D953 /* Modules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
|
13AEE6182D2A75110096D953 /* Modules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = "<group>"; };
|
||||||
13AEE61A2D2A78050096D953 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
13AEE61A2D2A78050096D953 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
13AEE61C2D2A78160096D953 /* JSController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; };
|
13AEE61C2D2A78160096D953 /* JSController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -107,9 +109,18 @@
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
133D7C1C2D2ADF060075467E /* MediaInfoView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
133D7C1D2D2ADF110075467E /* MediaInfoView.swift */,
|
||||||
|
);
|
||||||
|
path = MediaInfoView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
13AEE61E2D2AAD1E0096D953 /* Views */ = {
|
13AEE61E2D2AAD1E0096D953 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
133D7C1C2D2ADF060075467E /* MediaInfoView */,
|
||||||
13AEE62A2D2ABCB40096D953 /* SettingsView */,
|
13AEE62A2D2ABCB40096D953 /* SettingsView */,
|
||||||
13AEE6242D2AB1730096D953 /* HomeView.swift */,
|
13AEE6242D2AB1730096D953 /* HomeView.swift */,
|
||||||
13AEE6262D2AB1990096D953 /* LibraryView.swift */,
|
13AEE6262D2AB1990096D953 /* LibraryView.swift */,
|
||||||
|
|
@ -237,6 +248,7 @@
|
||||||
13AEE6252D2AB1730096D953 /* HomeView.swift in Sources */,
|
13AEE6252D2AB1730096D953 /* HomeView.swift in Sources */,
|
||||||
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */,
|
13AEE61B2D2A78050096D953 /* SettingsView.swift in Sources */,
|
||||||
1329D5C72D298198008AEDA2 /* Sora_JSApp.swift in Sources */,
|
1329D5C72D298198008AEDA2 /* Sora_JSApp.swift in Sources */,
|
||||||
|
133D7C1E2D2ADF110075467E /* MediaInfoView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,14 @@ struct ContentView: View {
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Home", systemImage: "house")
|
Label("Home", systemImage: "house")
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryView()
|
LibraryView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Library", systemImage: "books.vertical")
|
Label("Library", systemImage: "books.vertical")
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchView()
|
SearchView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Search", systemImage: "magnifyingglass")
|
Label("Search", systemImage: "magnifyingglass")
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Settings", systemImage: "gear")
|
Label("Settings", systemImage: "gear")
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class JSController: ObservableObject {
|
||||||
context.evaluateScript(script)
|
context.evaluateScript(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchContent(keyword: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
|
func fetchSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
|
||||||
let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
|
let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
|
||||||
|
|
||||||
guard let url = URL(string: searchUrl) else {
|
guard let url = URL(string: searchUrl) else {
|
||||||
|
|
@ -51,17 +51,63 @@ class JSController: ObservableObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let parseFunction = self.context.objectForKeyedSubscript("parseHTML"),
|
if let parseFunction = self.context.objectForKeyedSubscript("searchResults"),
|
||||||
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
|
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
|
||||||
let mediaItems = results.map { item in
|
let mediaItems = results.map { item in
|
||||||
MediaItem(
|
MediaItem(
|
||||||
title: item["title"] ?? "",
|
title: item["title"] ?? "",
|
||||||
imageUrl: item["image"] ?? ""
|
imageUrl: item["image"] ?? "",
|
||||||
|
href: item["href"] ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(mediaItems)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Failed to parse results")
|
||||||
|
DispatchQueue.main.async { completion([]) }
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchInfoContent(href: String, module: ScrapingModule, completion: @escaping ([MediaItem]) -> Void) {
|
||||||
|
var baseUrl = module.metadata.baseUrl
|
||||||
|
if !baseUrl.hasSuffix("/") && !href.hasPrefix("/") {
|
||||||
|
baseUrl += "/"
|
||||||
|
}
|
||||||
|
baseUrl += href
|
||||||
|
|
||||||
|
guard let url = URL(string: baseUrl) else {
|
||||||
|
completion([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
URLSession.custom.dataTask(with: url) { [weak self] data, response, 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("fetchInfo"),
|
||||||
|
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
|
||||||
|
let mediaItems = results.map { item in
|
||||||
|
MediaItem(
|
||||||
|
title: item["title"] ?? "",
|
||||||
|
imageUrl: item["image"] ?? "",
|
||||||
|
href: item["href"] ?? ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion(mediaItems)
|
completion(mediaItems)
|
||||||
print(mediaItems)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("Failed to parse results")
|
print("Failed to parse results")
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ struct ScrapingModule: Codable, Identifiable, Hashable {
|
||||||
|
|
||||||
class ModuleManager: ObservableObject {
|
class ModuleManager: ObservableObject {
|
||||||
@Published var modules: [ScrapingModule] = []
|
@Published var modules: [ScrapingModule] = []
|
||||||
|
|
||||||
private let fileManager = FileManager.default
|
private let fileManager = FileManager.default
|
||||||
private let modulesFileName = "modules.json"
|
private let modulesFileName = "modules.json"
|
||||||
|
|
||||||
|
|
|
||||||
53
Sora-JS/Views/MediaInfoView/MediaInfoView.swift
Normal file
53
Sora-JS/Views/MediaInfoView/MediaInfoView.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// MediaInfoView.swift
|
||||||
|
// Sora-JS
|
||||||
|
//
|
||||||
|
// Created by Francesco on 05/01/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct MediaInfoView: View {
|
||||||
|
let title: String
|
||||||
|
let imageUrl: String
|
||||||
|
let href: String
|
||||||
|
let module: ScrapingModule
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
KFImage(URL(string: imageUrl))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.largeTitle)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
var finalHref = href
|
||||||
|
if !href.starts(with: "http") {
|
||||||
|
var baseUrl = module.metadata.baseUrl
|
||||||
|
if !baseUrl.hasSuffix("/") && !href.hasPrefix("/") {
|
||||||
|
baseUrl += "/"
|
||||||
|
}
|
||||||
|
finalHref = baseUrl + href
|
||||||
|
}
|
||||||
|
if let url = URL(string: finalHref) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Open Link")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.navigationTitle("Media Info")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,15 +12,18 @@ struct MediaItem: Identifiable {
|
||||||
let id = UUID()
|
let id = UUID()
|
||||||
let title: String
|
let title: String
|
||||||
let imageUrl: String
|
let imageUrl: String
|
||||||
|
let href: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SearchView: View {
|
struct SearchView: View {
|
||||||
|
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||||
@StateObject private var jsController = JSController()
|
@StateObject private var jsController = JSController()
|
||||||
@EnvironmentObject var moduleManager: ModuleManager
|
@EnvironmentObject var moduleManager: ModuleManager
|
||||||
@State private var searchText = ""
|
|
||||||
@State private var mediaItems: [MediaItem] = []
|
@State private var mediaItems: [MediaItem] = []
|
||||||
|
@State private var selectedMediaItem: MediaItem?
|
||||||
@State private var isSearching = false
|
@State private var isSearching = false
|
||||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
@State private var searchText = ""
|
||||||
|
|
||||||
private var selectedModule: ScrapingModule? {
|
private var selectedModule: ScrapingModule? {
|
||||||
guard let id = selectedModuleId else { return nil }
|
guard let id = selectedModuleId else { return nil }
|
||||||
|
|
@ -69,6 +72,9 @@ struct SearchView: View {
|
||||||
.padding([.leading, .bottom], 8)
|
.padding([.leading, .bottom], 8)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
selectedMediaItem = item
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
@ -111,6 +117,9 @@ struct SearchView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(item: $selectedMediaItem) { item in
|
||||||
|
MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
.onChange(of: selectedModuleId) { _ in
|
.onChange(of: selectedModuleId) { _ in
|
||||||
|
|
@ -132,7 +141,7 @@ struct SearchView: View {
|
||||||
do {
|
do {
|
||||||
let jsContent = try moduleManager.getModuleContent(module)
|
let jsContent = try moduleManager.getModuleContent(module)
|
||||||
jsController.loadScript(jsContent)
|
jsController.loadScript(jsContent)
|
||||||
jsController.searchContent(keyword: searchText, module: module) { items in
|
jsController.fetchSearchResults(keyword: searchText, module: module) { items in
|
||||||
mediaItems = items
|
mediaItems = items
|
||||||
isSearching = false
|
isSearching = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ import SwiftUI
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
|
|
||||||
struct SettingsViewModule: View {
|
struct SettingsViewModule: View {
|
||||||
@EnvironmentObject var moduleManager: ModuleManager
|
|
||||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||||
@State private var isLoading = false
|
@EnvironmentObject var moduleManager: ModuleManager
|
||||||
|
|
||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
|
@State private var isLoading = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue