From a5c71c674376483ffe4d24e86e3c31d6ba65c93b Mon Sep 17 00:00:00 2001 From: Hamzenis Kryeziu Date: Tue, 25 Mar 2025 01:10:59 +0100 Subject: [PATCH 01/21] Added POST, PUT, PATCH Support - "fetchv2" javascript function now supports proper methods, header and body support - Backwards compatibility with previous implementation - Defaults to GET if only url is provided --- .../JavaScriptCore+Extensions.swift | 124 +++++++++++++----- 1 file changed, 93 insertions(+), 31 deletions(-) diff --git a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift index 553a7e7..947350c 100644 --- a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift @@ -78,65 +78,127 @@ extension JSContext { } func setupFetchV2() { - let fetchV2NativeFunction: @convention(block) (String, [String: String]?, JSValue, JSValue) -> Void = { urlString, headers, resolve, reject in + let fetchV2NativeFunction: @convention(block) (String, [String: String]?, String?, String?, JSValue, JSValue) -> Void = { urlString, headers, method, body, resolve, reject in guard let url = URL(string: urlString) else { Logger.shared.log("Invalid URL", type: "Error") reject.call(withArguments: ["Invalid URL"]) return } + + let httpMethod = method ?? "GET" var request = URLRequest(url: url) + request.httpMethod = httpMethod + + Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: "Debug") + + // Ensure no body for GET requests + if httpMethod == "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" { + Logger.shared.log("GET request must not have a body", type: "Error") + reject.call(withArguments: ["GET request must not have a body"]) + return + } + + // Set the body for non-GET requests + if httpMethod != "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" { + request.httpBody = body.data(using: .utf8) + } + + + // Set headers if let headers = headers { for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } } - let task = URLSession.cloudflareCustom.dataTask(with: request) { data, response, error in + + let task = URLSession.cloudflareCustom.downloadTask(with: request) { tempFileURL, response, error in if let error = error { Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) return } - guard let data = data else { + + guard let tempFileURL = tempFileURL else { Logger.shared.log("No data in response", type: "Error") reject.call(withArguments: ["No data"]) return } - // Just pass the raw data string and let JavaScript handle it - if let text = String(data: data, encoding: .utf8) { - resolve.call(withArguments: [text]) - } else { - Logger.shared.log("Unable to decode data to text", type: "Error") - reject.call(withArguments: ["Unable to decode data"]) + do { + let data = try Data(contentsOf: tempFileURL) + + // Check response size before processing + if data.count > 10_000_000 { // Example: 10MB limit + Logger.shared.log("Response exceeds maximum size", type: "Error") + reject.call(withArguments: ["Response exceeds maximum size"]) + return + } + + if let text = String(data: data, encoding: .utf8) { + resolve.call(withArguments: [text]) + } else { + Logger.shared.log("Unable to decode data to text", type: "Error") + reject.call(withArguments: ["Unable to decode data"]) + } + + } catch { + Logger.shared.log("Error reading downloaded file: \(error.localizedDescription)", type: "Error") + reject.call(withArguments: ["Error reading downloaded file"]) } } task.resume() } + + self.setObject(fetchV2NativeFunction, forKeyedSubscript: "fetchV2Native" as NSString) - // Simpler fetchv2 implementation with text() and json() methods let fetchv2Definition = """ - function fetchv2(url, headers) { - return new Promise(function(resolve, reject) { - fetchV2Native(url, headers, function(rawText) { - const responseObj = { - _data: rawText, - text: function() { - return Promise.resolve(this._data); - }, - json: function() { - try { - return Promise.resolve(JSON.parse(this._data)); - } catch (e) { - return Promise.reject("JSON parse error: " + e.message); - } - } - }; - resolve(responseObj); - }, reject); - }); - } - """ + function fetchv2(url, headers = {}, method = "GET", body = null) { + if (method === "GET") { + return new Promise(function(resolve, reject) { + fetchV2Native(url, headers, method, null, function(rawText) { // Pass `null` explicitly + const responseObj = { + _data: rawText, + text: function() { + return Promise.resolve(this._data); + }, + json: function() { + try { + return Promise.resolve(JSON.parse(this._data)); + } catch (e) { + return Promise.reject("JSON parse error: " + e.message); + } + } + }; + resolve(responseObj); + }, reject); + }); + } + + // Ensure body is properly serialized + const processedBody = body ? JSON.stringify(body) : null; + + return new Promise(function(resolve, reject) { + fetchV2Native(url, headers, method, processedBody, function(rawText) { + const responseObj = { + _data: rawText, + text: function() { + return Promise.resolve(this._data); + }, + json: function() { + try { + return Promise.resolve(JSON.parse(this._data)); + } catch (e) { + return Promise.reject("JSON parse error: " + e.message); + } + } + }; + resolve(responseObj); + }, reject); + }); + } + + """ self.evaluateScript(fetchv2Definition) } From 5826f89866103583a8aad1b5708979c2046451dd Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Tue, 25 Mar 2025 16:04:26 +0100 Subject: [PATCH 02/21] yeah idk tf is this --- .../AccentColor.colorset/Contents.json | 12 +- Sora/ContentView.swift | 32 +- Sora/Views/OnBoardingView.swift | 295 ++++++++++++++++++ .../SettingsViewGeneral.swift | 1 - Sulfur.xcodeproj/project.pbxproj | 4 + 5 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 Sora/Views/OnBoardingView.swift diff --git a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json b/Sora/Assets.xcassets/AccentColor.colorset/Contents.json index bb57667..929c9c5 100644 --- a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Sora/Assets.xcassets/AccentColor.colorset/Contents.json @@ -2,8 +2,13 @@ "colors" : [ { "color" : { - "platform" : "universal", - "reference" : "systemMintColor" + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.231", + "green" : "0.620", + "red" : "0.976" + } }, "idiom" : "universal" } @@ -11,5 +16,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "localizable" : true } } diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift index afbd4ea..6f7b2f2 100644 --- a/Sora/ContentView.swift +++ b/Sora/ContentView.swift @@ -9,20 +9,28 @@ import SwiftUI import Kingfisher struct ContentView: View { + @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding: Bool = false + var body: some View { - TabView { - LibraryView() - .tabItem { - Label("Library", systemImage: "books.vertical") - } - SearchView() - .tabItem { - Label("Search", systemImage: "magnifyingglass") - } - SettingsView() - .tabItem { - Label("Settings", systemImage: "gear") + Group { + if !hasCompletedOnboarding { + OnboardingView(hasCompletedOnboarding: $hasCompletedOnboarding) + } else { + TabView { + LibraryView() + .tabItem { + Label("Library", systemImage: "books.vertical") + } + SearchView() + .tabItem { + Label("Search", systemImage: "magnifyingglass") + } + SettingsView() + .tabItem { + Label("Settings", systemImage: "gear") + } } + } } } } diff --git a/Sora/Views/OnBoardingView.swift b/Sora/Views/OnBoardingView.swift new file mode 100644 index 0000000..243b825 --- /dev/null +++ b/Sora/Views/OnBoardingView.swift @@ -0,0 +1,295 @@ +// +// OnBoardingView.swift +// Sulfur +// +// Created by Francesco on 25/03/25. +// + +import SwiftUI + +struct OnboardingView: View { + @Binding var hasCompletedOnboarding: Bool + @State private var currentPage = 0 + + @EnvironmentObject var settings: Settings + + private var totalPages: Int { + onboardingScreens.count + 1 + } + + var body: some View { + ZStack { + Color(.systemBackground) + .edgesIgnoringSafeArea(.all) + + VStack { + HStack { + Spacer() + Button(action: { + withAnimation { + hasCompletedOnboarding = true + UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding") + } + }) { + Text("Skip") + .foregroundColor(.accentColor) + .padding() + } + } + + TabView(selection: $currentPage) { + ForEach(0.. 0 { + Button(action: { + withAnimation { currentPage -= 1 } + }) { + Text("Back") + } + .buttonStyle(PlainButtonStyle()) + .padding() + } + Spacer() + Button(action: { + withAnimation { + if currentPage == totalPages - 1 { + hasCompletedOnboarding = true + UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding") + } else { + currentPage += 1 + } + } + }) { + Text(currentPage == totalPages - 1 ? "Get Started" : "Continue") + } + .buttonStyle(FilledButtonStyle()) + } + .padding(.horizontal) + } + } + } +} + +struct PageIndicatorView: View { + let currentPage: Int + let totalPages: Int + + var body: some View { + HStack(spacing: 8) { + ForEach(0.. some View { + configuration.label + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding() + .background(Color.accentColor) + .cornerRadius(12) + .scaleEffect(configuration.isPressed ? 0.95 : 1.0) + .animation(.spring(), value: configuration.isPressed) + } +} + +struct OnboardingPage { + let imageName: String + let title: String + let description: String +} + +let onboardingScreens = [ + OnboardingPage( + imageName: "puzzlepiece.fill", + title: "Modular Web Scraping", + description: "Sora is a powerful, open-source web scraping app that works exclusively with custom modules." + ), + OnboardingPage( + imageName: "display", + title: "Multi-Platform Support", + description: "Enjoy Sora on iOS, iPadOS 15.0+ and macOS 12.0+. A flexible app for all your devices." + ), + OnboardingPage( + imageName: "play.circle.fill", + title: "Diverse Media Playback", + description: "Stream content from Jellyfin/Plex servers or any module and play media in external players like VLC, Infuse, and nPlayer or directly with the Sora or iOS player" + ), + OnboardingPage( + imageName: "lock.shield.fill", + title: "Privacy First", + description: "No subscriptions, no logins, no data collection. Sora prioritizes your privacy and will always be free and open source under the GPLv3.0 License." + ) +] + +struct OnboardingCustomizeAppearanceView: View { + @EnvironmentObject var settings: Settings + + @AppStorage("alwaysLandscape") private var isAlwaysLandscape = false + + @AppStorage("externalPlayer") private var externalPlayer: String = "Sora" + + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + + private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] + + var body: some View { + VStack { + Text("Customize Sora") + .font(.largeTitle) + .fontWeight(.bold) + .multilineTextAlignment(.center) + .padding(.top, 20) + + VStack(spacing: 20) { + SettingsSection(title: "Theme") { + ColorPicker("Accent Color", selection: $settings.accentColor) + + Picker("Appearance", selection: $settings.selectedAppearance) { + Text("System").tag(Appearance.system) + Text("Light").tag(Appearance.light) + Text("Dark").tag(Appearance.dark) + } + .pickerStyle(SegmentedPickerStyle()) + } + + SettingsSection(title: "Media Player") { + HStack { + Text("Media Player") + Spacer() + Menu(externalPlayer) { + ForEach(mediaPlayers, id: \.self) { provider in + Button(action: { + externalPlayer = provider + }) { + Text(provider) + } + } + } + } + + Toggle("Force Landscape", isOn: $isAlwaysLandscape) + .tint(.accentColor) + } + + SettingsSection(title: "Grid Layout") { + HStack { + Text("Portrait Columns") + Spacer() + Picker("", selection: $mediaColumnsPortrait) { + if UIDevice.current.userInterfaceIdiom == .pad { + ForEach(1..<6) { i in + Text("\(i)").tag(i) + } + } else { + ForEach(1..<5) { i in + Text("\(i)").tag(i) + } + } + } + .pickerStyle(MenuPickerStyle()) + .labelsHidden() + } + + HStack { + Text("Landscape Columns") + Spacer() + Picker("", selection: $mediaColumnsLandscape) { + if UIDevice.current.userInterfaceIdiom == .pad { + ForEach(2..<9) { i in + Text("\(i)").tag(i) + } + } else { + ForEach(2..<6) { i in + Text("\(i)").tag(i) + } + } + } + .pickerStyle(MenuPickerStyle()) + .labelsHidden() + } + } + } + .padding() + .background( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(Color(UIColor.secondarySystemBackground)) + .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5) + ) + .padding(.horizontal) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +struct SettingsSection: View { + let title: String + let content: Content + + init(title: String, @ViewBuilder content: () -> Content) { + self.title = title + self.content = content() + } + + var body: some View { + VStack(alignment: .center, spacing: 10) { + Text(title + ":") + .font(.headline) + + content + + Divider() + } + } +} diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index ed0f83e..b3b38f4 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -70,7 +70,6 @@ struct SettingsViewGeneral: View { } } } - } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index c38b27b..92dd81e 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 139935662D468C450065CEFF /* ModuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139935652D468C450065CEFF /* ModuleManager.swift */; }; 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */; }; 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; }; + 13ADAE022D92ED46007DCE7D /* OnBoardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */; }; 13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B7F4C02D58FFDD0045714A /* Shimmer.swift */; }; 13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */; }; 13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; }; @@ -99,6 +100,7 @@ 139935652D468C450065CEFF /* ModuleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleManager.swift; sourceTree = ""; }; 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = ""; }; 1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingView.swift; sourceTree = ""; }; 13B7F4C02D58FFDD0045714A /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = ""; }; 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingManager.swift; sourceTree = ""; }; 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingItem.swift; sourceTree = ""; }; @@ -254,6 +256,7 @@ 133F55B92D33B53E00E08EEA /* LibraryView */, 133D7C7C2D2BE2630075467E /* SearchView.swift */, 130217CB2D81C55E0011EFF5 /* DownloadView.swift */, + 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */, ); path = Views; sourceTree = ""; @@ -525,6 +528,7 @@ 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */, + 13ADAE022D92ED46007DCE7D /* OnBoardingView.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, 13DB46902D900A38008CBC03 /* URL.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */, From 43c0509b9339a10efadf837e6b236a9779855e21 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Tue, 25 Mar 2025 16:30:41 +0100 Subject: [PATCH 03/21] less stuffs --- Sora/Views/OnBoardingView.swift | 42 --------------------------------- 1 file changed, 42 deletions(-) diff --git a/Sora/Views/OnBoardingView.swift b/Sora/Views/OnBoardingView.swift index 243b825..e07b7dd 100644 --- a/Sora/Views/OnBoardingView.swift +++ b/Sora/Views/OnBoardingView.swift @@ -178,10 +178,6 @@ struct OnboardingCustomizeAppearanceView: View { @AppStorage("alwaysLandscape") private var isAlwaysLandscape = false @AppStorage("externalPlayer") private var externalPlayer: String = "Sora" - - @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 - @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 - private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] var body: some View { @@ -222,44 +218,6 @@ struct OnboardingCustomizeAppearanceView: View { Toggle("Force Landscape", isOn: $isAlwaysLandscape) .tint(.accentColor) } - - SettingsSection(title: "Grid Layout") { - HStack { - Text("Portrait Columns") - Spacer() - Picker("", selection: $mediaColumnsPortrait) { - if UIDevice.current.userInterfaceIdiom == .pad { - ForEach(1..<6) { i in - Text("\(i)").tag(i) - } - } else { - ForEach(1..<5) { i in - Text("\(i)").tag(i) - } - } - } - .pickerStyle(MenuPickerStyle()) - .labelsHidden() - } - - HStack { - Text("Landscape Columns") - Spacer() - Picker("", selection: $mediaColumnsLandscape) { - if UIDevice.current.userInterfaceIdiom == .pad { - ForEach(2..<9) { i in - Text("\(i)").tag(i) - } - } else { - ForEach(2..<6) { i in - Text("\(i)").tag(i) - } - } - } - .pickerStyle(MenuPickerStyle()) - .labelsHidden() - } - } } .padding() .background( From e06751d458f9bfcb991bcaa5b4de1ec7597750e9 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:06:07 +0100 Subject: [PATCH 04/21] Revert "less stuffs" This reverts commit 43c0509b9339a10efadf837e6b236a9779855e21. --- Sora/Views/OnBoardingView.swift | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Sora/Views/OnBoardingView.swift b/Sora/Views/OnBoardingView.swift index e07b7dd..243b825 100644 --- a/Sora/Views/OnBoardingView.swift +++ b/Sora/Views/OnBoardingView.swift @@ -178,6 +178,10 @@ struct OnboardingCustomizeAppearanceView: View { @AppStorage("alwaysLandscape") private var isAlwaysLandscape = false @AppStorage("externalPlayer") private var externalPlayer: String = "Sora" + + @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 + @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] var body: some View { @@ -218,6 +222,44 @@ struct OnboardingCustomizeAppearanceView: View { Toggle("Force Landscape", isOn: $isAlwaysLandscape) .tint(.accentColor) } + + SettingsSection(title: "Grid Layout") { + HStack { + Text("Portrait Columns") + Spacer() + Picker("", selection: $mediaColumnsPortrait) { + if UIDevice.current.userInterfaceIdiom == .pad { + ForEach(1..<6) { i in + Text("\(i)").tag(i) + } + } else { + ForEach(1..<5) { i in + Text("\(i)").tag(i) + } + } + } + .pickerStyle(MenuPickerStyle()) + .labelsHidden() + } + + HStack { + Text("Landscape Columns") + Spacer() + Picker("", selection: $mediaColumnsLandscape) { + if UIDevice.current.userInterfaceIdiom == .pad { + ForEach(2..<9) { i in + Text("\(i)").tag(i) + } + } else { + ForEach(2..<6) { i in + Text("\(i)").tag(i) + } + } + } + .pickerStyle(MenuPickerStyle()) + .labelsHidden() + } + } } .padding() .background( From 0b9dae6b98b04e600bd049de2e4c344833b8d2d7 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:06:10 +0100 Subject: [PATCH 05/21] Revert "yeah idk tf is this" This reverts commit 5826f89866103583a8aad1b5708979c2046451dd. --- .../AccentColor.colorset/Contents.json | 12 +- Sora/ContentView.swift | 32 +- Sora/Views/OnBoardingView.swift | 295 ------------------ .../SettingsViewGeneral.swift | 1 + Sulfur.xcodeproj/project.pbxproj | 4 - 5 files changed, 15 insertions(+), 329 deletions(-) delete mode 100644 Sora/Views/OnBoardingView.swift diff --git a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json b/Sora/Assets.xcassets/AccentColor.colorset/Contents.json index 929c9c5..bb57667 100644 --- a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Sora/Assets.xcassets/AccentColor.colorset/Contents.json @@ -2,13 +2,8 @@ "colors" : [ { "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.231", - "green" : "0.620", - "red" : "0.976" - } + "platform" : "universal", + "reference" : "systemMintColor" }, "idiom" : "universal" } @@ -16,8 +11,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "localizable" : true } } diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift index 6f7b2f2..afbd4ea 100644 --- a/Sora/ContentView.swift +++ b/Sora/ContentView.swift @@ -9,28 +9,20 @@ import SwiftUI import Kingfisher struct ContentView: View { - @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding: Bool = false - var body: some View { - Group { - if !hasCompletedOnboarding { - OnboardingView(hasCompletedOnboarding: $hasCompletedOnboarding) - } else { - TabView { - LibraryView() - .tabItem { - Label("Library", systemImage: "books.vertical") - } - SearchView() - .tabItem { - Label("Search", systemImage: "magnifyingglass") - } - SettingsView() - .tabItem { - Label("Settings", systemImage: "gear") - } + TabView { + LibraryView() + .tabItem { + Label("Library", systemImage: "books.vertical") + } + SearchView() + .tabItem { + Label("Search", systemImage: "magnifyingglass") + } + SettingsView() + .tabItem { + Label("Settings", systemImage: "gear") } - } } } } diff --git a/Sora/Views/OnBoardingView.swift b/Sora/Views/OnBoardingView.swift deleted file mode 100644 index 243b825..0000000 --- a/Sora/Views/OnBoardingView.swift +++ /dev/null @@ -1,295 +0,0 @@ -// -// OnBoardingView.swift -// Sulfur -// -// Created by Francesco on 25/03/25. -// - -import SwiftUI - -struct OnboardingView: View { - @Binding var hasCompletedOnboarding: Bool - @State private var currentPage = 0 - - @EnvironmentObject var settings: Settings - - private var totalPages: Int { - onboardingScreens.count + 1 - } - - var body: some View { - ZStack { - Color(.systemBackground) - .edgesIgnoringSafeArea(.all) - - VStack { - HStack { - Spacer() - Button(action: { - withAnimation { - hasCompletedOnboarding = true - UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding") - } - }) { - Text("Skip") - .foregroundColor(.accentColor) - .padding() - } - } - - TabView(selection: $currentPage) { - ForEach(0.. 0 { - Button(action: { - withAnimation { currentPage -= 1 } - }) { - Text("Back") - } - .buttonStyle(PlainButtonStyle()) - .padding() - } - Spacer() - Button(action: { - withAnimation { - if currentPage == totalPages - 1 { - hasCompletedOnboarding = true - UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding") - } else { - currentPage += 1 - } - } - }) { - Text(currentPage == totalPages - 1 ? "Get Started" : "Continue") - } - .buttonStyle(FilledButtonStyle()) - } - .padding(.horizontal) - } - } - } -} - -struct PageIndicatorView: View { - let currentPage: Int - let totalPages: Int - - var body: some View { - HStack(spacing: 8) { - ForEach(0.. some View { - configuration.label - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(Color.accentColor) - .cornerRadius(12) - .scaleEffect(configuration.isPressed ? 0.95 : 1.0) - .animation(.spring(), value: configuration.isPressed) - } -} - -struct OnboardingPage { - let imageName: String - let title: String - let description: String -} - -let onboardingScreens = [ - OnboardingPage( - imageName: "puzzlepiece.fill", - title: "Modular Web Scraping", - description: "Sora is a powerful, open-source web scraping app that works exclusively with custom modules." - ), - OnboardingPage( - imageName: "display", - title: "Multi-Platform Support", - description: "Enjoy Sora on iOS, iPadOS 15.0+ and macOS 12.0+. A flexible app for all your devices." - ), - OnboardingPage( - imageName: "play.circle.fill", - title: "Diverse Media Playback", - description: "Stream content from Jellyfin/Plex servers or any module and play media in external players like VLC, Infuse, and nPlayer or directly with the Sora or iOS player" - ), - OnboardingPage( - imageName: "lock.shield.fill", - title: "Privacy First", - description: "No subscriptions, no logins, no data collection. Sora prioritizes your privacy and will always be free and open source under the GPLv3.0 License." - ) -] - -struct OnboardingCustomizeAppearanceView: View { - @EnvironmentObject var settings: Settings - - @AppStorage("alwaysLandscape") private var isAlwaysLandscape = false - - @AppStorage("externalPlayer") private var externalPlayer: String = "Sora" - - @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 - @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 - - private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] - - var body: some View { - VStack { - Text("Customize Sora") - .font(.largeTitle) - .fontWeight(.bold) - .multilineTextAlignment(.center) - .padding(.top, 20) - - VStack(spacing: 20) { - SettingsSection(title: "Theme") { - ColorPicker("Accent Color", selection: $settings.accentColor) - - Picker("Appearance", selection: $settings.selectedAppearance) { - Text("System").tag(Appearance.system) - Text("Light").tag(Appearance.light) - Text("Dark").tag(Appearance.dark) - } - .pickerStyle(SegmentedPickerStyle()) - } - - SettingsSection(title: "Media Player") { - HStack { - Text("Media Player") - Spacer() - Menu(externalPlayer) { - ForEach(mediaPlayers, id: \.self) { provider in - Button(action: { - externalPlayer = provider - }) { - Text(provider) - } - } - } - } - - Toggle("Force Landscape", isOn: $isAlwaysLandscape) - .tint(.accentColor) - } - - SettingsSection(title: "Grid Layout") { - HStack { - Text("Portrait Columns") - Spacer() - Picker("", selection: $mediaColumnsPortrait) { - if UIDevice.current.userInterfaceIdiom == .pad { - ForEach(1..<6) { i in - Text("\(i)").tag(i) - } - } else { - ForEach(1..<5) { i in - Text("\(i)").tag(i) - } - } - } - .pickerStyle(MenuPickerStyle()) - .labelsHidden() - } - - HStack { - Text("Landscape Columns") - Spacer() - Picker("", selection: $mediaColumnsLandscape) { - if UIDevice.current.userInterfaceIdiom == .pad { - ForEach(2..<9) { i in - Text("\(i)").tag(i) - } - } else { - ForEach(2..<6) { i in - Text("\(i)").tag(i) - } - } - } - .pickerStyle(MenuPickerStyle()) - .labelsHidden() - } - } - } - .padding() - .background( - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(UIColor.secondarySystemBackground)) - .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5) - ) - .padding(.horizontal) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -struct SettingsSection: View { - let title: String - let content: Content - - init(title: String, @ViewBuilder content: () -> Content) { - self.title = title - self.content = content() - } - - var body: some View { - VStack(alignment: .center, spacing: 10) { - Text(title + ":") - .font(.headline) - - content - - Divider() - } - } -} diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index b3b38f4..ed0f83e 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -70,6 +70,7 @@ struct SettingsViewGeneral: View { } } } + } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 92dd81e..c38b27b 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -41,7 +41,6 @@ 139935662D468C450065CEFF /* ModuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139935652D468C450065CEFF /* ModuleManager.swift */; }; 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */; }; 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1399FAD52D3AB3DB00E97C31 /* Logger.swift */; }; - 13ADAE022D92ED46007DCE7D /* OnBoardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */; }; 13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B7F4C02D58FFDD0045714A /* Shimmer.swift */; }; 13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */; }; 13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; }; @@ -100,7 +99,6 @@ 139935652D468C450065CEFF /* ModuleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleManager.swift; sourceTree = ""; }; 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewLogger.swift; sourceTree = ""; }; 1399FAD52D3AB3DB00E97C31 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingView.swift; sourceTree = ""; }; 13B7F4C02D58FFDD0045714A /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = ""; }; 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingManager.swift; sourceTree = ""; }; 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingItem.swift; sourceTree = ""; }; @@ -256,7 +254,6 @@ 133F55B92D33B53E00E08EEA /* LibraryView */, 133D7C7C2D2BE2630075467E /* SearchView.swift */, 130217CB2D81C55E0011EFF5 /* DownloadView.swift */, - 13ADAE012D92ED46007DCE7D /* OnBoardingView.swift */, ); path = Views; sourceTree = ""; @@ -528,7 +525,6 @@ 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 1334FF4F2D786C9E007E289F /* TMDB-Trending.swift in Sources */, - 13ADAE022D92ED46007DCE7D /* OnBoardingView.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, 13DB46902D900A38008CBC03 /* URL.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */, From 37425a2e55c1ef08d40abbfc32d160ae0b8f9d05 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:39:49 +0100 Subject: [PATCH 06/21] idk maybe i've added DNS services? Yeah it should --- Sora/Utils/Extensions/URLSession.swift | 63 ++++++++++++++++--- .../Modules/ModuleAdditionSettingsView.swift | 2 +- Sora/Utils/Modules/ModuleManager.swift | 8 +-- .../SettingsViewGeneral.swift | 19 +++++- 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift index 9144c9f..d51eb38 100644 --- a/Sora/Utils/Extensions/URLSession.swift +++ b/Sora/Utils/Extensions/URLSession.swift @@ -5,10 +5,55 @@ // Created by Francesco on 05/01/25. // -import Foundation import Network +import Foundation + +enum DNSProvider: String, CaseIterable, Hashable { + case cloudflare = "Cloudflare" + case google = "Google" + case openDNS = "OpenDNS" + case quad9 = "Quad9" + case adGuard = "AdGuard" + case cleanbrowsing = "CleanBrowsing" + case controld = "ControlD" + + var servers: [String] { + switch self { + case .cloudflare: + return ["1.1.1.1", "1.0.0.1"] + case .google: + return ["8.8.8.8", "8.8.4.4"] + case .openDNS: + return ["208.67.222.222", "208.67.220.220"] + case .quad9: + return ["9.9.9.9", "149.112.112.112"] + case .adGuard: + return ["94.140.14.14", "94.140.15.15"] + case .cleanbrowsing: + return ["185.228.168.168", "185.228.169.168"] + case .controld: + return ["76.76.2.0", "76.76.10.0"] + } + } +} extension URLSession { + private static let dnsSelectorKey = "CustomDNSProvider" + + static var currentDNSProvider: DNSProvider { + get { + guard let savedProviderRawValue = UserDefaults.standard.string(forKey: dnsSelectorKey) else { + UserDefaults.standard.set(DNSProvider.cloudflare.rawValue, forKey: dnsSelectorKey) + return .cloudflare + } + + return DNSProvider(rawValue: savedProviderRawValue) ?? .cloudflare + } + set { + UserDefaults.standard.set(newValue.rawValue, forKey: dnsSelectorKey) + } + } + 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", @@ -35,31 +80,33 @@ extension URLSession { "Mozilla/5.0 (Android 13; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0" ] - static let randomUserAgent: String = { + static var randomUserAgent: String { userAgents.randomElement() ?? userAgents[0] - }() + } - static let custom: URLSession = { + static var custom: URLSession { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "User-Agent": randomUserAgent ] return URLSession(configuration: configuration) - }() + } - static let cloudflareCustom: URLSession = { + static var cloudflareCustom: URLSession { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "User-Agent": randomUserAgent ] + let dnsServers = currentDNSProvider.servers + let dnsSettings: [AnyHashable: Any] = [ "DNSSettings": [ - "ServerAddresses": ["1.1.1.1", "1.0.0.1"] + "ServerAddresses": dnsServers ] ] configuration.connectionProxyDictionary = dnsSettings return URLSession(configuration: configuration) - }() + } } diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index b2580e2..143cd94 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -154,7 +154,7 @@ struct ModuleAdditionSettingsView: View { return } do { - let (data, _) = try await URLSession.custom.data(from: url) + let (data, _) = try await URLSession.cloudflareCustom.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: data) await MainActor.run { self.moduleMetadata = metadata diff --git a/Sora/Utils/Modules/ModuleManager.swift b/Sora/Utils/Modules/ModuleManager.swift index d13d8cf..ada264c 100644 --- a/Sora/Utils/Modules/ModuleManager.swift +++ b/Sora/Utils/Modules/ModuleManager.swift @@ -46,14 +46,14 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Module already exists", code: -1) } - let (metadataData, _) = try await URLSession.custom.data(from: url) + let (metadataData, _) = try await URLSession.cloudflareCustom.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) guard let scriptUrl = URL(string: metadata.scriptUrl) else { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.cloudflareCustom.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } @@ -94,7 +94,7 @@ class ModuleManager: ObservableObject { func refreshModules() async { for (index, module) in modules.enumerated() { do { - let (metadataData, _) = try await URLSession.custom.data(from: URL(string: module.metadataUrl)!) + let (metadataData, _) = try await URLSession.cloudflareCustom.data(from: URL(string: module.metadataUrl)!) let newMetadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) if newMetadata.version != module.metadata.version { @@ -102,7 +102,7 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.cloudflareCustom.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index ed0f83e..310e5f3 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -14,9 +14,11 @@ struct SettingsViewGeneral: View { @AppStorage("analyticsEnabled") private var analyticsEnabled: Bool = false @AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false @AppStorage("metadataProviders") private var metadataProviders: String = "AniList" + @AppStorage("CustomDNSProvider") private var customDNSProvider: String = "Cloudflare" @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 + private let customDNSProviderList = ["Cloudflare", "Google", "OpenDNS", "Quad9", "AdGuard", "CleanBrowsing", "ControlD"] private let metadataProvidersList = ["AniList"] @EnvironmentObject var settings: Settings @@ -70,7 +72,6 @@ struct SettingsViewGeneral: View { } } } - } } @@ -121,9 +122,23 @@ struct SettingsViewGeneral: View { .tint(.accentColor) } - Section(header: Text("Analytics"), footer: Text("Allow Sora to collect anonymous data to improve the app. No personal information is collected. This can be disabled at any time.\n\n Information collected: \n- App version\n- Device model\n- Module Name/Version\n- Error Messages\n- Title of Watched Content")) { + Section(header: Text("Advanced"), footer: Text("Thanks to this Sora to collect anonymous data to improve the app. No personal information is collected. This can be disabled at any time.\n\n Information collected: \n- App version\n- Device model\n- Module Name/Version\n- Error Messages\n- Title of Watched Content")) { Toggle("Enable Analytics", isOn: $analyticsEnabled) .tint(.accentColor) + + HStack { + Text("DNS service") + Spacer() + Menu(customDNSProvider) { + ForEach(customDNSProviderList, id: \.self) { provider in + Button(action: { + customDNSProvider = provider + }) { + Text(provider) + } + } + } + } } } .navigationTitle("General") From 4ace715a8fbfa60381d9a93ba6f435937a8eeebd Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:40:22 +0100 Subject: [PATCH 07/21] Update SettingsView.swift --- Sora/Views/SettingsView/SettingsView.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sora/Views/SettingsView/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift index f7f6ec0..eacb628 100644 --- a/Sora/Views/SettingsView/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -11,9 +11,9 @@ struct SettingsView: View { var body: some View { NavigationView { Form { - Section(header: Text("Main Settings")) { + Section(header: Text("Main")) { NavigationLink(destination: SettingsViewGeneral()) { - Text("General Settings") + Text("General Preferences") } NavigationLink(destination: SettingsViewPlayer()) { Text("Media Player") @@ -21,9 +21,9 @@ struct SettingsView: View { NavigationLink(destination: SettingsViewModule()) { Text("Modules") } - NavigationLink(destination: SettingsViewTrackers()) { - Text("Trackers") - } + //NavigationLink(destination: SettingsViewTrackers()) { + // Text("Trackers") + //} } Section(header: Text("Info")) { From ec6c8c94eb7b95ad03ad945ffb66f0b5754819b2 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:47:35 +0100 Subject: [PATCH 08/21] fixed stream crash --- Sora/Views/MediaInfoView/MediaInfoView.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 3db0025..1a1d27d 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -626,9 +626,15 @@ struct MediaInfoView: View { UIApplication.shared.open(url, options: [:], completionHandler: nil) Logger.shared.log("Opening external app with scheme: \(url)", type: "General") } else { + guard let url = URL(string: url) else { + Logger.shared.log("Invalid stream URL: \(url)", type: "Error") + DropManager.shared.showDrop(title: "Error", subtitle: "Invalid stream URL", duration: 2.0, icon: UIImage(systemName: "xmark.circle")) + return + } + let customMediaPlayer = CustomMediaPlayerViewController( module: module, - urlString: url, + urlString: url.absoluteString, fullUrl: fullURL, title: title, episodeNumber: selectedEpisodeNumber, @@ -644,6 +650,9 @@ struct MediaInfoView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil) + } else { + Logger.shared.log("Failed to find root view controller", type: "Error") + DropManager.shared.showDrop(title: "Error", subtitle: "Failed to present player", duration: 2.0, icon: UIImage(systemName: "xmark.circle")) } } } From 11bcb663d266d81bf224a4711321532605803344 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:23:12 +0100 Subject: [PATCH 09/21] dns settings now actually work --- Sora/Info.plist | 2 - Sora/SoraApp.swift | 4 + .../JavaScriptCore+Extensions.swift | 4 +- Sora/Utils/Extensions/URLSession.swift | 82 +----- Sora/Utils/JSLoader/JSController.swift | 8 +- .../Helpers/VTTSubtitlesLoader.swift | 2 +- .../Modules/ModuleAdditionSettingsView.swift | 2 +- Sora/Utils/Modules/ModuleManager.swift | 8 +- Sora/Utils/NetworkDns/CustomDNS.swift | 248 ++++++++++++++++++ Sulfur.xcodeproj/project.pbxproj | 11 +- .../xcshareddata/swiftpm/Package.resolved | 79 +++--- 11 files changed, 325 insertions(+), 125 deletions(-) create mode 100644 Sora/Utils/NetworkDns/CustomDNS.swift diff --git a/Sora/Info.plist b/Sora/Info.plist index b085ddf..df1d517 100644 --- a/Sora/Info.plist +++ b/Sora/Info.plist @@ -2,8 +2,6 @@ - NSCameraUsageDescription - Sora may requires access to your device's camera. BGTaskSchedulerPermittedIdentifiers $(PRODUCT_BUNDLE_IDENTIFIER) diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 4073e20..34ababb 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -13,6 +13,10 @@ struct SoraApp: App { @StateObject private var moduleManager = ModuleManager() @StateObject private var librarykManager = LibraryManager() + init() { + registerCustomDNSGlobally() + } + var body: some Scene { WindowGroup { ContentView() diff --git a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift index 947350c..d30569b 100644 --- a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift @@ -45,7 +45,7 @@ extension JSContext { request.setValue(value, forHTTPHeaderField: key) } } - let task = URLSession.cloudflareCustom.dataTask(with: request) { data, _, error in + let task = URLSession.customDNS.dataTask(with: request) { data, _, error in if let error = error { Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) @@ -111,7 +111,7 @@ extension JSContext { } } - let task = URLSession.cloudflareCustom.downloadTask(with: request) { tempFileURL, response, error in + let task = URLSession.customDNS.downloadTask(with: request) { tempFileURL, response, error in if let error = error { Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift index d51eb38..187128c 100644 --- a/Sora/Utils/Extensions/URLSession.swift +++ b/Sora/Utils/Extensions/URLSession.swift @@ -5,55 +5,9 @@ // Created by Francesco on 05/01/25. // -import Network import Foundation -enum DNSProvider: String, CaseIterable, Hashable { - case cloudflare = "Cloudflare" - case google = "Google" - case openDNS = "OpenDNS" - case quad9 = "Quad9" - case adGuard = "AdGuard" - case cleanbrowsing = "CleanBrowsing" - case controld = "ControlD" - - var servers: [String] { - switch self { - case .cloudflare: - return ["1.1.1.1", "1.0.0.1"] - case .google: - return ["8.8.8.8", "8.8.4.4"] - case .openDNS: - return ["208.67.222.222", "208.67.220.220"] - case .quad9: - return ["9.9.9.9", "149.112.112.112"] - case .adGuard: - return ["94.140.14.14", "94.140.15.15"] - case .cleanbrowsing: - return ["185.228.168.168", "185.228.169.168"] - case .controld: - return ["76.76.2.0", "76.76.10.0"] - } - } -} - extension URLSession { - private static let dnsSelectorKey = "CustomDNSProvider" - - static var currentDNSProvider: DNSProvider { - get { - guard let savedProviderRawValue = UserDefaults.standard.string(forKey: dnsSelectorKey) else { - UserDefaults.standard.set(DNSProvider.cloudflare.rawValue, forKey: dnsSelectorKey) - return .cloudflare - } - - return DNSProvider(rawValue: savedProviderRawValue) ?? .cloudflare - } - set { - UserDefaults.standard.set(newValue.rawValue, forKey: dnsSelectorKey) - } - } - 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", @@ -80,33 +34,21 @@ extension URLSession { "Mozilla/5.0 (Android 13; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0" ] - static var randomUserAgent: String { + static let randomUserAgent: String = { userAgents.randomElement() ?? userAgents[0] - } + }() - static var custom: URLSession { + static let custom: URLSession = { let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = [ - "User-Agent": randomUserAgent - ] + configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent] return URLSession(configuration: configuration) - } + }() - static var cloudflareCustom: URLSession { - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = [ - "User-Agent": randomUserAgent - ] - - let dnsServers = currentDNSProvider.servers - - let dnsSettings: [AnyHashable: Any] = [ - "DNSSettings": [ - "ServerAddresses": dnsServers - ] - ] - - configuration.connectionProxyDictionary = dnsSettings - return URLSession(configuration: configuration) - } + static let customDNS: URLSession = { + let config = URLSessionConfiguration.default + var protocols = config.protocolClasses ?? [] + protocols.insert(CustomURLProtocol.self, at: 0) + config.protocolClasses = protocols + return URLSession(configuration: config, delegate: InsecureSessionDelegate(), delegateQueue: nil) + }() } diff --git a/Sora/Utils/JSLoader/JSController.swift b/Sora/Utils/JSLoader/JSController.swift index 6cf5e82..8edbda2 100644 --- a/Sora/Utils/JSLoader/JSController.swift +++ b/Sora/Utils/JSLoader/JSController.swift @@ -36,7 +36,7 @@ class JSController: ObservableObject { return } - URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in + URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -77,7 +77,7 @@ class JSController: ObservableObject { return } - URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in + URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -130,7 +130,7 @@ class JSController: ObservableObject { return } - URLSession.cloudflareCustom.dataTask(with: url) { [weak self] data, _, error in + URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -430,7 +430,7 @@ class JSController: ObservableObject { func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, completion: @escaping ((stream: String?, subtitles: String?)) -> Void) { let url = URL(string: episodeUrl)! - let task = URLSession.cloudflareCustom.dataTask(with: url) { data, response, error in + let task = URLSession.customDNS.dataTask(with: url) { data, response, error in if let error = error { Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error") DispatchQueue.main.async { completion((nil, nil)) } diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/VTTSubtitlesLoader.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/VTTSubtitlesLoader.swift index 944f2de..d457d7a 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/VTTSubtitlesLoader.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/VTTSubtitlesLoader.swift @@ -29,7 +29,7 @@ class VTTSubtitlesLoader: ObservableObject { let format = determineSubtitleFormat(from: url) - URLSession.cloudflareCustom.dataTask(with: url) { data, _, error in + URLSession.shared.dataTask(with: url) { data, _, error in guard let data = data, let content = String(data: data, encoding: .utf8), error == nil else { return } diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index 143cd94..c7f6d96 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -154,7 +154,7 @@ struct ModuleAdditionSettingsView: View { return } do { - let (data, _) = try await URLSession.cloudflareCustom.data(from: url) + let (data, _) = try await URLSession.customDNS.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: data) await MainActor.run { self.moduleMetadata = metadata diff --git a/Sora/Utils/Modules/ModuleManager.swift b/Sora/Utils/Modules/ModuleManager.swift index ada264c..9bf815c 100644 --- a/Sora/Utils/Modules/ModuleManager.swift +++ b/Sora/Utils/Modules/ModuleManager.swift @@ -46,14 +46,14 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Module already exists", code: -1) } - let (metadataData, _) = try await URLSession.cloudflareCustom.data(from: url) + let (metadataData, _) = try await URLSession.customDNS.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) guard let scriptUrl = URL(string: metadata.scriptUrl) else { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.cloudflareCustom.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.customDNS.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } @@ -94,7 +94,7 @@ class ModuleManager: ObservableObject { func refreshModules() async { for (index, module) in modules.enumerated() { do { - let (metadataData, _) = try await URLSession.cloudflareCustom.data(from: URL(string: module.metadataUrl)!) + let (metadataData, _) = try await URLSession.customDNS.data(from: URL(string: module.metadataUrl)!) let newMetadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) if newMetadata.version != module.metadata.version { @@ -102,7 +102,7 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.cloudflareCustom.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.customDNS.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } diff --git a/Sora/Utils/NetworkDns/CustomDNS.swift b/Sora/Utils/NetworkDns/CustomDNS.swift new file mode 100644 index 0000000..ee41506 --- /dev/null +++ b/Sora/Utils/NetworkDns/CustomDNS.swift @@ -0,0 +1,248 @@ +// +// CustomDNS.swift +// Sora +// +// Created by Seiike on 26/03/25. +// + +import Foundation +import Network + +enum DNSProvider: String, CaseIterable, Hashable { + case cloudflare = "Cloudflare" + case google = "Google" + case openDNS = "OpenDNS" + case quad9 = "Quad9" + case adGuard = "AdGuard" + case cleanbrowsing = "CleanBrowsing" + case controld = "ControlD" + + var servers: [String] { + switch self { + case .cloudflare: + return ["1.1.1.1", "1.0.0.1"] + case .google: + return ["8.8.8.8", "8.8.4.4"] + case .openDNS: + return ["208.67.222.222", "208.67.220.220"] + case .quad9: + return ["9.9.9.9", "149.112.112.112"] + case .adGuard: + return ["94.140.14.14", "94.140.15.15"] + case .cleanbrowsing: + return ["185.228.168.168", "185.228.169.168"] + case .controld: + return ["76.76.2.0", "76.76.10.0"] + } + } + + static var current: DNSProvider { + get { + let raw = UserDefaults.standard.string(forKey: "SelectedDNSProvider") ?? DNSProvider.cloudflare.rawValue + return DNSProvider(rawValue: raw) ?? .cloudflare + } + set { + UserDefaults.standard.setValue(newValue.rawValue, forKey: "SelectedDNSProvider") + } + } +} + +class CustomDNSResolver { + var dnsServerIP: String { + return DNSProvider.current.servers.first ?? "1.1.1.1" + } + + func buildDNSQuery(for host: String) -> (Data, UInt16) { + var data = Data() + let queryID = UInt16.random(in: 0...UInt16.max) + data.append(UInt8(queryID >> 8)) + data.append(UInt8(queryID & 0xFF)) + data.append(contentsOf: [0x01, 0x00]) + data.append(contentsOf: [0x00, 0x01]) + data.append(contentsOf: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + let labels = host.split(separator: ".") + for label in labels { + if let labelData = label.data(using: .utf8) { + data.append(UInt8(labelData.count)) + data.append(labelData) + } + } + data.append(0) + data.append(contentsOf: [0x00, 0x01]) + data.append(contentsOf: [0x00, 0x01]) + return (data, queryID) + } + + func parseDNSResponse(_ data: Data, queryID: UInt16) -> [String] { + var ips = [String]() + var offset = 0 + func readUInt16() -> UInt16? { + guard offset + 2 <= data.count else { return nil } + let value = (UInt16(data[offset]) << 8) | UInt16(data[offset+1]) + offset += 2 + return value + } + func readUInt32() -> UInt32? { + guard offset + 4 <= data.count else { return nil } + let value = (UInt32(data[offset]) << 24) | (UInt32(data[offset+1]) << 16) | (UInt32(data[offset+2]) << 8) | UInt32(data[offset+3]) + offset += 4 + return value + } + guard data.count >= 12 else { return [] } + let responseID = (UInt16(data[0]) << 8) | UInt16(data[1]) + if responseID != queryID { return [] } + offset = 2 + offset += 2 + guard let qdCount = readUInt16() else { return [] } + guard let anCount = readUInt16() else { return [] } + offset += 4 + for _ in 0..) -> Void) { + let dnsServer = self.dnsServerIP + guard let port = NWEndpoint.Port(rawValue: 53) else { + completion(.failure(NSError(domain: "CustomDNS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid port"]))) + return + } + let connection = NWConnection(host: NWEndpoint.Host(dnsServer), port: port, using: .udp) + connection.stateUpdateHandler = { newState in + switch newState { + case .ready: + let (queryData, queryID) = self.buildDNSQuery(for: host) + connection.send(content: queryData, completion: .contentProcessed({ error in + if let error = error { + completion(.failure(error)) + connection.cancel() + } else { + connection.receive(minimumIncompleteLength: 1, maximumLength: 512) { content, _, _, error in + if let error = error { + completion(.failure(error)) + } else if let content = content { + let ips = self.parseDNSResponse(content, queryID: queryID) + if !ips.isEmpty { + completion(.success(ips)) + } else { + completion(.failure(NSError(domain: "CustomDNS", code: 2, userInfo: [NSLocalizedDescriptionKey: "No A records found"]))) + } + } + connection.cancel() + } + } + })) + case .failed(let error): + completion(.failure(error)) + connection.cancel() + default: + break + } + } + connection.start(queue: DispatchQueue.global()) + } +} + +class CustomURLProtocol: URLProtocol { + static let resolver = CustomDNSResolver() + override class func canInit(with request: URLRequest) -> Bool { + return URLProtocol.property(forKey: "Handled", in: request) == nil + } + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + override func startLoading() { + guard let url = request.url, let host = url.host else { + client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -1, userInfo: nil)) + return + } + CustomURLProtocol.resolver.resolve(host: host) { result in + switch result { + case .success(let ips): + guard let ip = ips.first, + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -2, userInfo: nil)) + return + } + components.host = ip + guard let ipURL = components.url else { + self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -3, userInfo: nil)) + return + } + guard let mutableRequest = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else { + self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -4, userInfo: nil)) + return + } + mutableRequest.url = ipURL + mutableRequest.setValue(host, forHTTPHeaderField: "Host") + URLProtocol.setProperty(true, forKey: "Handled", in: mutableRequest) + let finalRequest = mutableRequest as URLRequest + let session = URLSession.customDNS + let task = session.dataTask(with: finalRequest) { data, response, error in + if let data = data, let response = response { + self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + self.client?.urlProtocol(self, didLoad: data) + self.client?.urlProtocolDidFinishLoading(self) + } else if let error = error { + self.client?.urlProtocol(self, didFailWithError: error) + } + } + task.resume() + case .failure(let error): + self.client?.urlProtocol(self, didFailWithError: error) + } + } + } + override func stopLoading() {} +} + +class InsecureSessionDelegate: NSObject, URLSessionDelegate { + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust { + let credential = URLCredential(trust: serverTrust) + completionHandler(.useCredential, credential) + } else { + completionHandler(.performDefaultHandling, nil) + } + } +} + +func registerCustomDNSGlobally() { + let config = URLSessionConfiguration.default + var protocols = config.protocolClasses ?? [] + protocols.insert(CustomURLProtocol.self, at: 0) + config.protocolClasses = protocols + URLSessionConfiguration.default.protocolClasses = protocols + URLSessionConfiguration.ephemeral.protocolClasses = protocols + URLSessionConfiguration.background(withIdentifier: "CustomDNSBackground").protocolClasses = protocols +} diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index c38b27b..ac91e90 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -122,6 +122,10 @@ 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 1EBA87D92D94653B00CABC28 /* NetworkDns */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = NetworkDns; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 133D7C672D2BE2500075467E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -294,6 +298,7 @@ 133D7C882D2BE2640075467E /* Modules */, 1399FAD12D3AB33D00E97C31 /* Logger */, 13D842532D45266900EBBFA6 /* Drops */, + 1EBA87D92D94653B00CABC28 /* NetworkDns */, ); path = Utils; sourceTree = ""; @@ -452,6 +457,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 1EBA87D92D94653B00CABC28 /* NetworkDns */, + ); name = Sulfur; packageProductDependencies = ( 133D7C962D2BE2AF0075467E /* Kingfisher */, @@ -701,13 +709,13 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; - DEVELOPMENT_TEAM = 399LMK6Q2Y; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Sora/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + INFOPLIST_KEY_NSCameraUsageDescription = "Sora may requires access to your device's camera."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -750,6 +758,7 @@ INFOPLIST_FILE = Sora/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + INFOPLIST_KEY_NSCameraUsageDescription = "Sora may requires access to your device's camera."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9784b35..fe8d570 100644 --- a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,43 +1,42 @@ { - "object": { - "pins": [ - { - "package": "Drops", - "repositoryURL": "https://github.com/omaralbeik/Drops.git", - "state": { - "branch": "main", - "revision": "5824681795286c36bdc4a493081a63e64e2a064e", - "version": null - } - }, - { - "package": "FFmpeg-iOS-Lame", - "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Lame", - "state": { - "branch": "main", - "revision": "1808fa5a1263c5e216646cd8421fc7dcb70520cc", - "version": null - } - }, - { - "package": "FFmpeg-iOS-Support", - "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Support", - "state": { - "branch": null, - "revision": "be3bd9149ac53760e8725652eee99c405b2be47a", - "version": "0.0.2" - } - }, - { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher.git", - "state": { - "branch": null, - "revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e", - "version": "7.9.1" - } + "originHash" : "28f2c123747ea3d0aee96430294eb72e7254bafd504c83303b2a2e02f270f26f", + "pins" : [ + { + "identity" : "drops", + "kind" : "remoteSourceControl", + "location" : "https://github.com/omaralbeik/Drops.git", + "state" : { + "branch" : "main", + "revision" : "5824681795286c36bdc4a493081a63e64e2a064e" } - ] - }, - "version": 1 + }, + { + "identity" : "ffmpeg-ios-lame", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kewlbear/FFmpeg-iOS-Lame", + "state" : { + "branch" : "main", + "revision" : "1808fa5a1263c5e216646cd8421fc7dcb70520cc" + } + }, + { + "identity" : "ffmpeg-ios-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kewlbear/FFmpeg-iOS-Support", + "state" : { + "revision" : "be3bd9149ac53760e8725652eee99c405b2be47a", + "version" : "0.0.2" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "b6f62758f21a8c03cd64f4009c037cfa580a256e", + "version" : "7.9.1" + } + } + ], + "version" : 3 } From fff3449174a51bffad2c3be8342cc61e2dd57310 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:52:12 +0100 Subject: [PATCH 10/21] prob very works --- Sora/Utils/NetworkDns/CustomDNS.swift | 20 ++++++- .../SettingsViewGeneral.swift | 59 +++++++------------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Sora/Utils/NetworkDns/CustomDNS.swift b/Sora/Utils/NetworkDns/CustomDNS.swift index ee41506..14e7f5d 100644 --- a/Sora/Utils/NetworkDns/CustomDNS.swift +++ b/Sora/Utils/NetworkDns/CustomDNS.swift @@ -48,8 +48,24 @@ enum DNSProvider: String, CaseIterable, Hashable { } class CustomDNSResolver { + // Use custom DNS servers if "Custom" is selected; otherwise, fall back to the default provider's servers. + var dnsServers: [String] { + if let provider = UserDefaults.standard.string(forKey: "CustomDNSProvider"), + provider == "Custom" { + let primary = UserDefaults.standard.string(forKey: "customPrimaryDNS") ?? "" + let secondary = UserDefaults.standard.string(forKey: "customSecondaryDNS") ?? "" + var servers = [String]() + if !primary.isEmpty { servers.append(primary) } + if !secondary.isEmpty { servers.append(secondary) } + if !servers.isEmpty { + return servers + } + } + return DNSProvider.current.servers + } + var dnsServerIP: String { - return DNSProvider.current.servers.first ?? "1.1.1.1" + return dnsServers.first ?? "1.1.1.1" } func buildDNSQuery(for host: String) -> (Data, UInt16) { @@ -74,6 +90,7 @@ class CustomDNSResolver { } func parseDNSResponse(_ data: Data, queryID: UInt16) -> [String] { + // Existing implementation remains unchanged. var ips = [String]() var offset = 0 func readUInt16() -> UInt16? { @@ -173,6 +190,7 @@ class CustomDNSResolver { } } + class CustomURLProtocol: URLProtocol { static let resolver = CustomDNSResolver() override class func canInit(with request: URLRequest) -> Bool { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index 310e5f3..fe08109 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -15,10 +15,12 @@ struct SettingsViewGeneral: View { @AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false @AppStorage("metadataProviders") private var metadataProviders: String = "AniList" @AppStorage("CustomDNSProvider") private var customDNSProvider: String = "Cloudflare" + @AppStorage("customPrimaryDNS") private var customPrimaryDNS: String = "" + @AppStorage("customSecondaryDNS") private var customSecondaryDNS: String = "" @AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2 @AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4 - private let customDNSProviderList = ["Cloudflare", "Google", "OpenDNS", "Quad9", "AdGuard", "CleanBrowsing", "ControlD"] + private let customDNSProviderList = ["Cloudflare", "Google", "OpenDNS", "Quad9", "AdGuard", "CleanBrowsing", "ControlD", "Custom"] private let metadataProvidersList = ["AniList"] @EnvironmentObject var settings: Settings @@ -26,7 +28,7 @@ struct SettingsViewGeneral: View { Form { Section(header: Text("Interface")) { ColorPicker("Accent Color", selection: $settings.accentColor) - HStack() { + HStack { Text("Appearance") Picker("Appearance", selection: $settings.selectedAppearance) { Text("System").tag(Appearance.system) @@ -42,18 +44,10 @@ struct SettingsViewGeneral: View { Text("Episodes Range") Spacer() Menu { - Button(action: { episodeChunkSize = 25 }) { - Text("25") - } - Button(action: { episodeChunkSize = 50 }) { - Text("50") - } - Button(action: { episodeChunkSize = 75 }) { - Text("75") - } - Button(action: { episodeChunkSize = 100 }) { - Text("100") - } + Button(action: { episodeChunkSize = 25 }) { Text("25") } + Button(action: { episodeChunkSize = 50 }) { Text("50") } + Button(action: { episodeChunkSize = 75 }) { Text("75") } + Button(action: { episodeChunkSize = 100 }) { Text("100") } } label: { Text("\(episodeChunkSize)") } @@ -65,9 +59,7 @@ struct SettingsViewGeneral: View { Spacer() Menu(metadataProviders) { ForEach(metadataProvidersList, id: \.self) { provider in - Button(action: { - metadataProviders = provider - }) { + Button(action: { metadataProviders = provider }) { Text(provider) } } @@ -75,25 +67,16 @@ struct SettingsViewGeneral: View { } } - //Section(header: Text("Downloads"), footer: Text("Note that the modules will be replaced only if there is a different version string inside the JSON file.")) { - // Toggle("Multi Threads conversion", isOn: $multiThreadsEnabled) - // .tint(.accentColor) - //} - Section(header: Text("Media Grid Layout"), footer: Text("Adjust the number of media items per row in portrait and landscape modes.")) { HStack { if UIDevice.current.userInterfaceIdiom == .pad { Picker("Portrait Columns", selection: $mediaColumnsPortrait) { - ForEach(1..<6) { i in - Text("\(i)").tag(i) - } + ForEach(1..<6) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } else { Picker("Portrait Columns", selection: $mediaColumnsPortrait) { - ForEach(1..<5) { i in - Text("\(i)").tag(i) - } + ForEach(1..<5) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } @@ -101,16 +84,12 @@ struct SettingsViewGeneral: View { HStack { if UIDevice.current.userInterfaceIdiom == .pad { Picker("Landscape Columns", selection: $mediaColumnsLandscape) { - ForEach(2..<9) { i in - Text("\(i)").tag(i) - } + ForEach(2..<9) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } else { Picker("Landscape Columns", selection: $mediaColumnsLandscape) { - ForEach(2..<6) { i in - Text("\(i)").tag(i) - } + ForEach(2..<6) { i in Text("\(i)").tag(i) } } .pickerStyle(MenuPickerStyle()) } @@ -122,7 +101,7 @@ struct SettingsViewGeneral: View { .tint(.accentColor) } - Section(header: Text("Advanced"), footer: Text("Thanks to this Sora to collect anonymous data to improve the app. No personal information is collected. This can be disabled at any time.\n\n Information collected: \n- App version\n- Device model\n- Module Name/Version\n- Error Messages\n- Title of Watched Content")) { + Section(header: Text("Advanced"), footer: Text("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.")) { Toggle("Enable Analytics", isOn: $analyticsEnabled) .tint(.accentColor) @@ -131,14 +110,18 @@ struct SettingsViewGeneral: View { Spacer() Menu(customDNSProvider) { ForEach(customDNSProviderList, id: \.self) { provider in - Button(action: { - customDNSProvider = provider - }) { + Button(action: { customDNSProvider = provider }) { Text(provider) } } } } + if customDNSProvider == "Custom" { + TextField("Primary DNS", text: $customPrimaryDNS) + .keyboardType(.numbersAndPunctuation) + TextField("Secondary DNS", text: $customSecondaryDNS) + .keyboardType(.numbersAndPunctuation) + } } } .navigationTitle("General") From 284bc11ad789ddbb2aa0f1c25f4557248ebde21a Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:01:35 +0100 Subject: [PATCH 11/21] =?UTF-8?q?fuck=20region=20restricions=20?= =?UTF-8?q?=F0=9F=93=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sora/Utils/NetworkDns/CustomDNS.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sora/Utils/NetworkDns/CustomDNS.swift b/Sora/Utils/NetworkDns/CustomDNS.swift index 14e7f5d..1b90529 100644 --- a/Sora/Utils/NetworkDns/CustomDNS.swift +++ b/Sora/Utils/NetworkDns/CustomDNS.swift @@ -4,6 +4,7 @@ // // Created by Seiike on 26/03/25. // +// fuck region restrictions import Foundation import Network From 4da986eaa32ded3299657d650b6555174ffc6796 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:10:19 +0100 Subject: [PATCH 12/21] fuck xcodeproj --- Sulfur.xcodeproj/project.pbxproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index ac91e90..bf4fa14 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -709,13 +709,13 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; + DEVELOPMENT_TEAM = 399LMK6Q2Y; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Sora/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; - INFOPLIST_KEY_NSCameraUsageDescription = "Sora may requires access to your device's camera."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -758,7 +758,6 @@ INFOPLIST_FILE = Sora/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; - INFOPLIST_KEY_NSCameraUsageDescription = "Sora may requires access to your device's camera."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; From fe874f3692bf37870e629c1e6b48ef1bdde052f0 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:29:45 +0100 Subject: [PATCH 13/21] ta fucking da --- .../CustomDNS.swift | 0 Sulfur.xcodeproj/project.pbxproj | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) rename Sora/Utils/{NetworkDns => NetworkDNS}/CustomDNS.swift (100%) diff --git a/Sora/Utils/NetworkDns/CustomDNS.swift b/Sora/Utils/NetworkDNS/CustomDNS.swift similarity index 100% rename from Sora/Utils/NetworkDns/CustomDNS.swift rename to Sora/Utils/NetworkDNS/CustomDNS.swift diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index bf4fa14..ec7d983 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; + 1ED156202D949B1A00C11BCD /* CustomDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -119,13 +120,10 @@ 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; + 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNS.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 1EBA87D92D94653B00CABC28 /* NetworkDns */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = NetworkDns; sourceTree = ""; }; -/* End PBXFileSystemSynchronizedRootGroup section */ - /* Begin PBXFrameworksBuildPhase section */ 133D7C672D2BE2500075467E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -288,6 +286,7 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( + 1ED1561E2D949AFB00C11BCD /* NetworkDNS */, 13DB7CEA2D7DED50004371D3 /* DownloadManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, @@ -298,7 +297,6 @@ 133D7C882D2BE2640075467E /* Modules */, 1399FAD12D3AB33D00E97C31 /* Logger */, 13D842532D45266900EBBFA6 /* Drops */, - 1EBA87D92D94653B00CABC28 /* NetworkDns */, ); path = Utils; sourceTree = ""; @@ -442,6 +440,14 @@ path = Components; sourceTree = ""; }; + 1ED1561E2D949AFB00C11BCD /* NetworkDNS */ = { + isa = PBXGroup; + children = ( + 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */, + ); + path = NetworkDNS; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -457,9 +463,6 @@ ); dependencies = ( ); - fileSystemSynchronizedGroups = ( - 1EBA87D92D94653B00CABC28 /* NetworkDns */, - ); name = Sulfur; packageProductDependencies = ( 133D7C962D2BE2AF0075467E /* Kingfisher */, @@ -570,6 +573,7 @@ 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */, 13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */, 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */, + 1ED156202D949B1A00C11BCD /* CustomDNS.swift in Sources */, 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */, 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */, 13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */, From ad96b5fdaad421338ee619be8f1ebe4e58b456e6 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:54:16 +0100 Subject: [PATCH 14/21] changed it to series https://discord.com/channels/1293430817841741899/1318240587886891029/1354558523824144504 --- Sora/Views/MediaInfoView/MediaInfoView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 1a1d27d..6e1af48 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -296,7 +296,7 @@ struct MediaInfoView: View { UserDefaults.standard.set(99999999.0, forKey: "totalTime_\(href)") } refreshTrigger.toggle() - Logger.shared.log("Marked \(ep.number - 1) episodes watched within anime \"\(title)\".", type: "General") + Logger.shared.log("Marked \(ep.number - 1) episodes watched within series \"\(title)\".", type: "General") } ) .id(refreshTrigger) From b1ce1e86dd2fb0ab4056befe5d9d8fa4cfd74096 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 26 Mar 2025 22:04:40 +0100 Subject: [PATCH 15/21] as cranci wanted --- .../SettingsSubViews/SettingsViewGeneral.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index fe08109..a3414dc 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -101,10 +101,7 @@ struct SettingsViewGeneral: View { .tint(.accentColor) } - Section(header: Text("Advanced"), footer: Text("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.")) { - Toggle("Enable Analytics", isOn: $analyticsEnabled) - .tint(.accentColor) - + Section(header: Text("Network"), footer: Text("Try between some of the providers in case something is not loading if it should be, as it might be the fault of your ISP.")){ HStack { Text("DNS service") Spacer() @@ -123,6 +120,11 @@ struct SettingsViewGeneral: View { .keyboardType(.numbersAndPunctuation) } } + + Section(header: Text("Advanced"), footer: Text("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.")) { + Toggle("Enable Analytics", isOn: $analyticsEnabled) + .tint(.accentColor) + } } .navigationTitle("General") } From d29d340d84909ff9e1ca4733d81f1caef4acc105 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:50:16 +0100 Subject: [PATCH 16/21] d --- Sora/Utils/NetworkDNS/CustomDNS.swift | 8 ++-- Sulfur.xcodeproj/project.pbxproj | 59 +-------------------------- 2 files changed, 4 insertions(+), 63 deletions(-) diff --git a/Sora/Utils/NetworkDNS/CustomDNS.swift b/Sora/Utils/NetworkDNS/CustomDNS.swift index 1b90529..98c7bc4 100644 --- a/Sora/Utils/NetworkDNS/CustomDNS.swift +++ b/Sora/Utils/NetworkDNS/CustomDNS.swift @@ -49,7 +49,6 @@ enum DNSProvider: String, CaseIterable, Hashable { } class CustomDNSResolver { - // Use custom DNS servers if "Custom" is selected; otherwise, fall back to the default provider's servers. var dnsServers: [String] { if let provider = UserDefaults.standard.string(forKey: "CustomDNSProvider"), provider == "Custom" { @@ -91,7 +90,6 @@ class CustomDNSResolver { } func parseDNSResponse(_ data: Data, queryID: UInt16) -> [String] { - // Existing implementation remains unchanged. var ips = [String]() var offset = 0 func readUInt16() -> UInt16? { @@ -210,9 +208,9 @@ class CustomURLProtocol: URLProtocol { case .success(let ips): guard let ip = ips.first, var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -2, userInfo: nil)) - return - } + self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -2, userInfo: nil)) + return + } components.host = ip guard let ipURL = components.url else { self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -3, userInfo: nil)) diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index ec7d983..fcf3d0a 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -31,10 +31,8 @@ 133D7C922D2BE2640075467E /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C872D2BE2640075467E /* URLSession.swift */; }; 133D7C932D2BE2640075467E /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C892D2BE2640075467E /* Modules.swift */; }; 133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; }; - 133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; }; 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; 1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; }; - 1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 1359ED192D76FA7D00C13034 /* Drops */; }; 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; }; 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; }; 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; }; @@ -53,7 +51,6 @@ 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 */; }; 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; }; 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; }; @@ -129,9 +126,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 13DB7CC62D7DC7D2004371D3 /* FFmpeg-iOS-Lame in Frameworks */, - 1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */, - 133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -286,10 +280,10 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( - 1ED1561E2D949AFB00C11BCD /* NetworkDNS */, 13DB7CEA2D7DED50004371D3 /* DownloadManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, + 1ED1561E2D949AFB00C11BCD /* NetworkDNS */, 13DC0C442D302C6A00D0F966 /* MediaPlayer */, 133D7C862D2BE2640075467E /* Extensions */, 1327FBA52D758CEA00FC6689 /* Analytics */, @@ -465,9 +459,6 @@ ); name = Sulfur; packageProductDependencies = ( - 133D7C962D2BE2AF0075467E /* Kingfisher */, - 1359ED192D76FA7D00C13034 /* Drops */, - 13DB7CC52D7DC7D2004371D3 /* FFmpeg-iOS-Lame */, ); productName = Sora; productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */; @@ -497,9 +488,6 @@ ); mainGroup = 133D7C612D2BE2500075467E; packageReferences = ( - 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */, - 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */, - 13DB7CC42D7DC7D2004371D3 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */, ); productRefGroup = 133D7C6B2D2BE2500075467E /* Products */; projectDirPath = ""; @@ -808,51 +796,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; - requirement = { - kind = exactVersion; - version = 7.9.1; - }; - }; - 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/omaralbeik/Drops.git"; - requirement = { - branch = main; - kind = branch; - }; - }; - 13DB7CC42D7DC7D2004371D3 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/kewlbear/FFmpeg-iOS-Lame"; - requirement = { - branch = main; - kind = branch; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 133D7C962D2BE2AF0075467E /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; - 1359ED192D76FA7D00C13034 /* Drops */ = { - isa = XCSwiftPackageProductDependency; - package = 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */; - productName = Drops; - }; - 13DB7CC52D7DC7D2004371D3 /* FFmpeg-iOS-Lame */ = { - isa = XCSwiftPackageProductDependency; - package = 13DB7CC42D7DC7D2004371D3 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */; - productName = "FFmpeg-iOS-Lame"; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 133D7C622D2BE2500075467E /* Project object */; } From 4764e16ccd0218c987b190d47cad2c0c640c24cc Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:55:25 +0100 Subject: [PATCH 17/21] =?UTF-8?q?fuck=20@Seeike=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sulfur.xcodeproj/project.pbxproj | 57 +++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 79 ++++++++++--------- 2 files changed, 97 insertions(+), 39 deletions(-) diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index fcf3d0a..d9c74ca 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 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 */; }; + 132E351D2D959DDB0007800E /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 132E351C2D959DDB0007800E /* Drops */; }; + 132E35202D959E1D0007800E /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */; }; + 132E35232D959E410007800E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 132E35222D959E410007800E /* Kingfisher */; }; 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 */; }; @@ -126,6 +129,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 132E35232D959E410007800E /* Kingfisher in Frameworks */, + 132E35202D959E1D0007800E /* FFmpeg-iOS-Lame in Frameworks */, + 132E351D2D959DDB0007800E /* Drops in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -459,6 +465,9 @@ ); name = Sulfur; packageProductDependencies = ( + 132E351C2D959DDB0007800E /* Drops */, + 132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */, + 132E35222D959E410007800E /* Kingfisher */, ); productName = Sora; productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */; @@ -488,6 +497,9 @@ ); mainGroup = 133D7C612D2BE2500075467E; packageReferences = ( + 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */, + 132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */, + 132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = 133D7C6B2D2BE2500075467E /* Products */; projectDirPath = ""; @@ -796,6 +808,51 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/omaralbeik/Drops.git"; + requirement = { + branch = main; + kind = branch; + }; + }; + 132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kewlbear/FFmpeg-iOS-Lame"; + requirement = { + branch = main; + kind = branch; + }; + }; + 132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = exactVersion; + version = 7.9.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 132E351C2D959DDB0007800E /* Drops */ = { + isa = XCSwiftPackageProductDependency; + package = 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */; + productName = Drops; + }; + 132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */ = { + isa = XCSwiftPackageProductDependency; + package = 132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */; + productName = "FFmpeg-iOS-Lame"; + }; + 132E35222D959E410007800E /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 133D7C622D2BE2500075467E /* Project object */; } diff --git a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fe8d570..9784b35 100644 --- a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,42 +1,43 @@ { - "originHash" : "28f2c123747ea3d0aee96430294eb72e7254bafd504c83303b2a2e02f270f26f", - "pins" : [ - { - "identity" : "drops", - "kind" : "remoteSourceControl", - "location" : "https://github.com/omaralbeik/Drops.git", - "state" : { - "branch" : "main", - "revision" : "5824681795286c36bdc4a493081a63e64e2a064e" + "object": { + "pins": [ + { + "package": "Drops", + "repositoryURL": "https://github.com/omaralbeik/Drops.git", + "state": { + "branch": "main", + "revision": "5824681795286c36bdc4a493081a63e64e2a064e", + "version": null + } + }, + { + "package": "FFmpeg-iOS-Lame", + "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Lame", + "state": { + "branch": "main", + "revision": "1808fa5a1263c5e216646cd8421fc7dcb70520cc", + "version": null + } + }, + { + "package": "FFmpeg-iOS-Support", + "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Support", + "state": { + "branch": null, + "revision": "be3bd9149ac53760e8725652eee99c405b2be47a", + "version": "0.0.2" + } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher.git", + "state": { + "branch": null, + "revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e", + "version": "7.9.1" + } } - }, - { - "identity" : "ffmpeg-ios-lame", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kewlbear/FFmpeg-iOS-Lame", - "state" : { - "branch" : "main", - "revision" : "1808fa5a1263c5e216646cd8421fc7dcb70520cc" - } - }, - { - "identity" : "ffmpeg-ios-support", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kewlbear/FFmpeg-iOS-Support", - "state" : { - "revision" : "be3bd9149ac53760e8725652eee99c405b2be47a", - "version" : "0.0.2" - } - }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "b6f62758f21a8c03cd64f4009c037cfa580a256e", - "version" : "7.9.1" - } - } - ], - "version" : 3 + ] + }, + "version": 1 } From 9bd4d6e74d42ebbbc11dd12658b22a07424137b2 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:59:34 +0100 Subject: [PATCH 18/21] fixed randomness again --- Sora/Utils/Extensions/URLSession.swift | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift index 187128c..50a32e1 100644 --- a/Sora/Utils/Extensions/URLSession.swift +++ b/Sora/Utils/Extensions/URLSession.swift @@ -9,32 +9,32 @@ import Foundation 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/125.0.0.0 Safari/537.36", - "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; 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" + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.2569.45", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.2478.89", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 15_1_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.1 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 15_0_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.0 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.1; rv:128.0) Gecko/20100101 Firefox/128.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.0; rv:127.0) Gecko/20100101 Firefox/127.0", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0", + "Mozilla/5.0 (Linux; Android 15; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6782.112 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6782.112 Mobile Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.3 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (iPad; CPU OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/19.3 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Android 15; Mobile; rv:128.0) Gecko/128.0 Firefox/128.0", + "Mozilla/5.0 (Android 15; Mobile; rv:127.0) Gecko/127.0 Firefox/127.0" ] - static let randomUserAgent: String = { + static var randomUserAgent: String = { userAgents.randomElement() ?? userAgents[0] }() @@ -51,4 +51,4 @@ extension URLSession { config.protocolClasses = protocols return URLSession(configuration: config, delegate: InsecureSessionDelegate(), delegateQueue: nil) }() -} +} \ No newline at end of file From 07b4a0280e898f0c4968679291b5a310caf26990 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:03:47 +0100 Subject: [PATCH 19/21] =?UTF-8?q?does=20ts=20even=20work=3F=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sora/Views/SearchView.swift | 46 ++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 50d5528..0b978b8 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -165,20 +165,38 @@ struct SearchView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { - ForEach(moduleManager.modules, id: \.id) { module in - Button { - selectedModuleId = module.id.uuidString - } label: { - HStack { - KFImage(URL(string: module.metadata.iconUrl)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - .cornerRadius(4) - Text(module.metadata.sourceName) - if module.id.uuidString == selectedModuleId { - Image(systemName: "checkmark") - .foregroundColor(.accentColor) + let modulesByLanguage = Dictionary(grouping: moduleManager.modules) { module in + guard let language = module.metadata.language else { return "Unknown" } + + let cleanLanguage = language.replacingOccurrences( + of: "\\s*\\([^\\)]*\\)", + with: "", + options: .regularExpression + ).trimmingCharacters(in: .whitespaces) + + return cleanLanguage.isEmpty ? "Unknown" : cleanLanguage + } + + let sortedLanguages = modulesByLanguage.keys.sorted() + + ForEach(sortedLanguages, id: \.self) { language in + Menu(language) { + ForEach(modulesByLanguage[language] ?? [], id: \.id) { module in + Button { + selectedModuleId = module.id.uuidString + } label: { + HStack { + KFImage(URL(string: module.metadata.iconUrl)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + .cornerRadius(4) + Text(module.metadata.sourceName) + if module.id.uuidString == selectedModuleId { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + } + } } } } From 6989fbfe8733046c75b69e1bdf2dee1995956eee Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:09:19 +0100 Subject: [PATCH 20/21] fixed maybe? at least on my machine --- .../CustomPlayer/CustomPlayer.swift | 12 ----- Sora/Views/SearchView.swift | 53 +++++++++++++------ 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 1acb788..f6e1f71 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -709,20 +709,16 @@ class CustomMediaPlayerViewController: UIViewController { ) } - // Watch Next Button Logic: - let hideNext = UserDefaults.standard.bool(forKey: "hideNextButton") let isNearEnd = (self.duration - self.currentTimeVal) <= (self.duration * 0.10) && self.currentTimeVal != self.duration && self.showWatchNextButton && self.duration != 0 if isNearEnd { - // First appearance: show the button in its normal position. if !self.isWatchNextVisible { self.isWatchNextVisible = true self.watchNextButtonAppearedAt = self.currentTimeVal - // Choose constraints based on current controls visibility. if self.isControlsVisible { NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) @@ -730,7 +726,6 @@ class CustomMediaPlayerViewController: UIViewController { NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints) NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints) } - // Soft fade-in. self.watchNextButton.isHidden = false self.watchNextButton.alpha = 0.0 UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { @@ -738,23 +733,19 @@ class CustomMediaPlayerViewController: UIViewController { }, completion: nil) } - // When 5 seconds have elapsed from when the button first appeared: if let appearedAt = self.watchNextButtonAppearedAt, (self.currentTimeVal - appearedAt) >= 5, !self.isWatchNextRepositioned { - // Fade out the button first (even if controls are visible). UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { self.watchNextButton.alpha = 0.0 }, completion: { _ in self.watchNextButton.isHidden = true - // Then lock it to the controls-attached constraints. NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) self.isWatchNextRepositioned = true }) } } else { - // Not near end: reset the watch-next button state. self.watchNextButtonAppearedAt = nil self.isWatchNextVisible = false self.isWatchNextRepositioned = false @@ -771,7 +762,6 @@ class CustomMediaPlayerViewController: UIViewController { func repositionWatchNextButton() { self.isWatchNextRepositioned = true - // Update constraints so the button is now attached next to the playback controls. UIView.animate(withDuration: 0.3, animations: { NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) @@ -799,7 +789,6 @@ class CustomMediaPlayerViewController: UIViewController { self.skip85Button.alpha = self.isControlsVisible ? 0.8 : 0 if self.isControlsVisible { - // Always use the controls-attached constraints. NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) if self.isWatchNextRepositioned || self.isWatchNextVisible { @@ -809,7 +798,6 @@ class CustomMediaPlayerViewController: UIViewController { }) } } else { - // When controls are hidden: if !self.isWatchNextRepositioned && self.isWatchNextVisible { NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints) NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints) diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index 0b978b8..cfd739d 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -165,23 +165,9 @@ struct SearchView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { - let modulesByLanguage = Dictionary(grouping: moduleManager.modules) { module in - guard let language = module.metadata.language else { return "Unknown" } - - let cleanLanguage = language.replacingOccurrences( - of: "\\s*\\([^\\)]*\\)", - with: "", - options: .regularExpression - ).trimmingCharacters(in: .whitespaces) - - return cleanLanguage.isEmpty ? "Unknown" : cleanLanguage - } - - let sortedLanguages = modulesByLanguage.keys.sorted() - - ForEach(sortedLanguages, id: \.self) { language in + ForEach(getModuleLanguageGroups(), id: \.self) { language in Menu(language) { - ForEach(modulesByLanguage[language] ?? [], id: \.id) { module in + ForEach(getModulesForLanguage(language), id: \.id) { module in Button { selectedModuleId = module.id.uuidString } label: { @@ -287,6 +273,41 @@ struct SearchView: View { return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait } } + + private func cleanLanguageName(_ language: String?) -> String { + guard let language = language else { return "Unknown" } + + let cleaned = language.replacingOccurrences( + of: "\\s*\\([^\\)]*\\)", + with: "", + options: .regularExpression + ).trimmingCharacters(in: .whitespaces) + + return cleaned.isEmpty ? "Unknown" : cleaned + } + + private func getModulesByLanguage() -> [String: [ScrapingModule]] { + var result = [String: [ScrapingModule]]() + + for module in moduleManager.modules { + let language = cleanLanguageName(module.metadata.language) + if result[language] == nil { + result[language] = [module] + } else { + result[language]?.append(module) + } + } + + return result + } + + private func getModuleLanguageGroups() -> [String] { + return getModulesByLanguage().keys.sorted() + } + + private func getModulesForLanguage(_ language: String) -> [ScrapingModule] { + return getModulesByLanguage()[language] ?? [] + } } struct SearchBar: View { From 2eb07aebf19e5b7ff3b9e84a8ee7b2379e4a2e09 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Fri, 28 Mar 2025 14:23:55 +0100 Subject: [PATCH 21/21] =?UTF-8?q?yeah=20fuck=20@Seeike=20not=20the=20ISPs?= =?UTF-8?q?=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sora/SoraApp.swift | 4 - .../JavaScriptCore+Extensions.swift | 17 +- Sora/Utils/Extensions/URLSession.swift | 10 +- Sora/Utils/JSLoader/JSController.swift | 8 +- .../Modules/ModuleAdditionSettingsView.swift | 2 +- Sora/Utils/Modules/ModuleManager.swift | 8 +- Sora/Utils/NetworkDNS/CustomDNS.swift | 265 ------------------ .../SettingsViewGeneral.swift | 20 -- Sulfur.xcodeproj/project.pbxproj | 12 - 9 files changed, 13 insertions(+), 333 deletions(-) delete mode 100644 Sora/Utils/NetworkDNS/CustomDNS.swift diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 34ababb..4073e20 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -13,10 +13,6 @@ struct SoraApp: App { @StateObject private var moduleManager = ModuleManager() @StateObject private var librarykManager = LibraryManager() - init() { - registerCustomDNSGlobally() - } - var body: some Scene { WindowGroup { ContentView() diff --git a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift index d30569b..eda6f2a 100644 --- a/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift +++ b/Sora/Utils/Extensions/JavaScriptCore+Extensions.swift @@ -11,13 +11,11 @@ extension JSContext { func setupConsoleLogging() { let consoleObject = JSValue(newObjectIn: self) - // Set up console.log let consoleLogFunction: @convention(block) (String) -> Void = { message in Logger.shared.log(message, type: "Debug") } consoleObject?.setObject(consoleLogFunction, forKeyedSubscript: "log" as NSString) - // Set up console.error let consoleErrorFunction: @convention(block) (String) -> Void = { message in Logger.shared.log(message, type: "Error") } @@ -25,7 +23,6 @@ extension JSContext { self.setObject(consoleObject, forKeyedSubscript: "console" as NSString) - // Global log function let logFunction: @convention(block) (String) -> Void = { message in Logger.shared.log("JavaScript log: \(message)", type: "Debug") } @@ -45,7 +42,7 @@ extension JSContext { request.setValue(value, forHTTPHeaderField: key) } } - let task = URLSession.customDNS.dataTask(with: request) { data, _, error in + let task = URLSession.custom.dataTask(with: request) { data, _, error in if let error = error { Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) @@ -91,27 +88,24 @@ extension JSContext { Logger.shared.log("FetchV2 Request: URL=\(url), Method=\(httpMethod), Body=\(body ?? "nil")", type: "Debug") - // Ensure no body for GET requests if httpMethod == "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" { Logger.shared.log("GET request must not have a body", type: "Error") reject.call(withArguments: ["GET request must not have a body"]) return } - // Set the body for non-GET requests if httpMethod != "GET", let body = body, !body.isEmpty, body != "null", body != "undefined" { request.httpBody = body.data(using: .utf8) } - // Set headers if let headers = headers { for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } } - let task = URLSession.customDNS.downloadTask(with: request) { tempFileURL, response, error in + let task = URLSession.custom.downloadTask(with: request) { tempFileURL, response, error in if let error = error { Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) @@ -127,8 +121,7 @@ extension JSContext { do { let data = try Data(contentsOf: tempFileURL) - // Check response size before processing - if data.count > 10_000_000 { // Example: 10MB limit + if data.count > 10_000_000 { Logger.shared.log("Response exceeds maximum size", type: "Error") reject.call(withArguments: ["Response exceeds maximum size"]) return @@ -203,7 +196,6 @@ extension JSContext { } func setupBase64Functions() { - // btoa function: converts binary string to base64-encoded ASCII string let btoaFunction: @convention(block) (String) -> String? = { data in guard let data = data.data(using: .utf8) else { Logger.shared.log("btoa: Failed to encode input as UTF-8", type: "Error") @@ -212,7 +204,6 @@ extension JSContext { return data.base64EncodedString() } - // atob function: decodes base64-encoded ASCII string to binary string let atobFunction: @convention(block) (String) -> String? = { base64String in guard let data = Data(base64Encoded: base64String) else { Logger.shared.log("atob: Invalid base64 input", type: "Error") @@ -222,12 +213,10 @@ extension JSContext { return String(data: data, encoding: .utf8) } - // Add the functions to the JavaScript context self.setObject(btoaFunction, forKeyedSubscript: "btoa" as NSString) self.setObject(atobFunction, forKeyedSubscript: "atob" as NSString) } - // Helper method to set up all JavaScript functionality func setupJavaScriptEnvironment() { setupConsoleLogging() setupNativeFetch() diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift index 50a32e1..544b084 100644 --- a/Sora/Utils/Extensions/URLSession.swift +++ b/Sora/Utils/Extensions/URLSession.swift @@ -43,12 +43,4 @@ extension URLSession { configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent] return URLSession(configuration: configuration) }() - - static let customDNS: URLSession = { - let config = URLSessionConfiguration.default - var protocols = config.protocolClasses ?? [] - protocols.insert(CustomURLProtocol.self, at: 0) - config.protocolClasses = protocols - return URLSession(configuration: config, delegate: InsecureSessionDelegate(), delegateQueue: nil) - }() -} \ No newline at end of file +} diff --git a/Sora/Utils/JSLoader/JSController.swift b/Sora/Utils/JSLoader/JSController.swift index 8edbda2..a0a220b 100644 --- a/Sora/Utils/JSLoader/JSController.swift +++ b/Sora/Utils/JSLoader/JSController.swift @@ -36,7 +36,7 @@ class JSController: ObservableObject { return } - URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in + URLSession.custom.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -77,7 +77,7 @@ class JSController: ObservableObject { return } - URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in + URLSession.custom.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -130,7 +130,7 @@ class JSController: ObservableObject { return } - URLSession.customDNS.dataTask(with: url) { [weak self] data, _, error in + URLSession.custom.dataTask(with: url) { [weak self] data, _, error in guard let self = self else { return } if let error = error { @@ -430,7 +430,7 @@ class JSController: ObservableObject { func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, completion: @escaping ((stream: String?, subtitles: String?)) -> Void) { let url = URL(string: episodeUrl)! - let task = URLSession.customDNS.dataTask(with: url) { data, response, error in + let task = URLSession.custom.dataTask(with: url) { data, response, error in if let error = error { Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error") DispatchQueue.main.async { completion((nil, nil)) } diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index c7f6d96..b2580e2 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -154,7 +154,7 @@ struct ModuleAdditionSettingsView: View { return } do { - let (data, _) = try await URLSession.customDNS.data(from: url) + let (data, _) = try await URLSession.custom.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: data) await MainActor.run { self.moduleMetadata = metadata diff --git a/Sora/Utils/Modules/ModuleManager.swift b/Sora/Utils/Modules/ModuleManager.swift index 9bf815c..d13d8cf 100644 --- a/Sora/Utils/Modules/ModuleManager.swift +++ b/Sora/Utils/Modules/ModuleManager.swift @@ -46,14 +46,14 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Module already exists", code: -1) } - let (metadataData, _) = try await URLSession.customDNS.data(from: url) + let (metadataData, _) = try await URLSession.custom.data(from: url) let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) guard let scriptUrl = URL(string: metadata.scriptUrl) else { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.customDNS.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } @@ -94,7 +94,7 @@ class ModuleManager: ObservableObject { func refreshModules() async { for (index, module) in modules.enumerated() { do { - let (metadataData, _) = try await URLSession.customDNS.data(from: URL(string: module.metadataUrl)!) + let (metadataData, _) = try await URLSession.custom.data(from: URL(string: module.metadataUrl)!) let newMetadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) if newMetadata.version != module.metadata.version { @@ -102,7 +102,7 @@ class ModuleManager: ObservableObject { throw NSError(domain: "Invalid script URL", code: -1) } - let (scriptData, _) = try await URLSession.customDNS.data(from: scriptUrl) + let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl) guard let jsContent = String(data: scriptData, encoding: .utf8) else { throw NSError(domain: "Invalid script encoding", code: -1) } diff --git a/Sora/Utils/NetworkDNS/CustomDNS.swift b/Sora/Utils/NetworkDNS/CustomDNS.swift deleted file mode 100644 index 98c7bc4..0000000 --- a/Sora/Utils/NetworkDNS/CustomDNS.swift +++ /dev/null @@ -1,265 +0,0 @@ -// -// CustomDNS.swift -// Sora -// -// Created by Seiike on 26/03/25. -// -// fuck region restrictions - -import Foundation -import Network - -enum DNSProvider: String, CaseIterable, Hashable { - case cloudflare = "Cloudflare" - case google = "Google" - case openDNS = "OpenDNS" - case quad9 = "Quad9" - case adGuard = "AdGuard" - case cleanbrowsing = "CleanBrowsing" - case controld = "ControlD" - - var servers: [String] { - switch self { - case .cloudflare: - return ["1.1.1.1", "1.0.0.1"] - case .google: - return ["8.8.8.8", "8.8.4.4"] - case .openDNS: - return ["208.67.222.222", "208.67.220.220"] - case .quad9: - return ["9.9.9.9", "149.112.112.112"] - case .adGuard: - return ["94.140.14.14", "94.140.15.15"] - case .cleanbrowsing: - return ["185.228.168.168", "185.228.169.168"] - case .controld: - return ["76.76.2.0", "76.76.10.0"] - } - } - - static var current: DNSProvider { - get { - let raw = UserDefaults.standard.string(forKey: "SelectedDNSProvider") ?? DNSProvider.cloudflare.rawValue - return DNSProvider(rawValue: raw) ?? .cloudflare - } - set { - UserDefaults.standard.setValue(newValue.rawValue, forKey: "SelectedDNSProvider") - } - } -} - -class CustomDNSResolver { - var dnsServers: [String] { - if let provider = UserDefaults.standard.string(forKey: "CustomDNSProvider"), - provider == "Custom" { - let primary = UserDefaults.standard.string(forKey: "customPrimaryDNS") ?? "" - let secondary = UserDefaults.standard.string(forKey: "customSecondaryDNS") ?? "" - var servers = [String]() - if !primary.isEmpty { servers.append(primary) } - if !secondary.isEmpty { servers.append(secondary) } - if !servers.isEmpty { - return servers - } - } - return DNSProvider.current.servers - } - - var dnsServerIP: String { - return dnsServers.first ?? "1.1.1.1" - } - - func buildDNSQuery(for host: String) -> (Data, UInt16) { - var data = Data() - let queryID = UInt16.random(in: 0...UInt16.max) - data.append(UInt8(queryID >> 8)) - data.append(UInt8(queryID & 0xFF)) - data.append(contentsOf: [0x01, 0x00]) - data.append(contentsOf: [0x00, 0x01]) - data.append(contentsOf: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) - let labels = host.split(separator: ".") - for label in labels { - if let labelData = label.data(using: .utf8) { - data.append(UInt8(labelData.count)) - data.append(labelData) - } - } - data.append(0) - data.append(contentsOf: [0x00, 0x01]) - data.append(contentsOf: [0x00, 0x01]) - return (data, queryID) - } - - func parseDNSResponse(_ data: Data, queryID: UInt16) -> [String] { - var ips = [String]() - var offset = 0 - func readUInt16() -> UInt16? { - guard offset + 2 <= data.count else { return nil } - let value = (UInt16(data[offset]) << 8) | UInt16(data[offset+1]) - offset += 2 - return value - } - func readUInt32() -> UInt32? { - guard offset + 4 <= data.count else { return nil } - let value = (UInt32(data[offset]) << 24) | (UInt32(data[offset+1]) << 16) | (UInt32(data[offset+2]) << 8) | UInt32(data[offset+3]) - offset += 4 - return value - } - guard data.count >= 12 else { return [] } - let responseID = (UInt16(data[0]) << 8) | UInt16(data[1]) - if responseID != queryID { return [] } - offset = 2 - offset += 2 - guard let qdCount = readUInt16() else { return [] } - guard let anCount = readUInt16() else { return [] } - offset += 4 - for _ in 0..) -> Void) { - let dnsServer = self.dnsServerIP - guard let port = NWEndpoint.Port(rawValue: 53) else { - completion(.failure(NSError(domain: "CustomDNS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid port"]))) - return - } - let connection = NWConnection(host: NWEndpoint.Host(dnsServer), port: port, using: .udp) - connection.stateUpdateHandler = { newState in - switch newState { - case .ready: - let (queryData, queryID) = self.buildDNSQuery(for: host) - connection.send(content: queryData, completion: .contentProcessed({ error in - if let error = error { - completion(.failure(error)) - connection.cancel() - } else { - connection.receive(minimumIncompleteLength: 1, maximumLength: 512) { content, _, _, error in - if let error = error { - completion(.failure(error)) - } else if let content = content { - let ips = self.parseDNSResponse(content, queryID: queryID) - if !ips.isEmpty { - completion(.success(ips)) - } else { - completion(.failure(NSError(domain: "CustomDNS", code: 2, userInfo: [NSLocalizedDescriptionKey: "No A records found"]))) - } - } - connection.cancel() - } - } - })) - case .failed(let error): - completion(.failure(error)) - connection.cancel() - default: - break - } - } - connection.start(queue: DispatchQueue.global()) - } -} - - -class CustomURLProtocol: URLProtocol { - static let resolver = CustomDNSResolver() - override class func canInit(with request: URLRequest) -> Bool { - return URLProtocol.property(forKey: "Handled", in: request) == nil - } - override class func canonicalRequest(for request: URLRequest) -> URLRequest { - return request - } - override func startLoading() { - guard let url = request.url, let host = url.host else { - client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -1, userInfo: nil)) - return - } - CustomURLProtocol.resolver.resolve(host: host) { result in - switch result { - case .success(let ips): - guard let ip = ips.first, - var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -2, userInfo: nil)) - return - } - components.host = ip - guard let ipURL = components.url else { - self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -3, userInfo: nil)) - return - } - guard let mutableRequest = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else { - self.client?.urlProtocol(self, didFailWithError: NSError(domain: "CustomDNS", code: -4, userInfo: nil)) - return - } - mutableRequest.url = ipURL - mutableRequest.setValue(host, forHTTPHeaderField: "Host") - URLProtocol.setProperty(true, forKey: "Handled", in: mutableRequest) - let finalRequest = mutableRequest as URLRequest - let session = URLSession.customDNS - let task = session.dataTask(with: finalRequest) { data, response, error in - if let data = data, let response = response { - self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) - self.client?.urlProtocol(self, didLoad: data) - self.client?.urlProtocolDidFinishLoading(self) - } else if let error = error { - self.client?.urlProtocol(self, didFailWithError: error) - } - } - task.resume() - case .failure(let error): - self.client?.urlProtocol(self, didFailWithError: error) - } - } - } - override func stopLoading() {} -} - -class InsecureSessionDelegate: NSObject, URLSessionDelegate { - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust { - let credential = URLCredential(trust: serverTrust) - completionHandler(.useCredential, credential) - } else { - completionHandler(.performDefaultHandling, nil) - } - } -} - -func registerCustomDNSGlobally() { - let config = URLSessionConfiguration.default - var protocols = config.protocolClasses ?? [] - protocols.insert(CustomURLProtocol.self, at: 0) - config.protocolClasses = protocols - URLSessionConfiguration.default.protocolClasses = protocols - URLSessionConfiguration.ephemeral.protocolClasses = protocols - URLSessionConfiguration.background(withIdentifier: "CustomDNSBackground").protocolClasses = protocols -} diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift index a3414dc..154d1d2 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewGeneral.swift @@ -101,26 +101,6 @@ struct SettingsViewGeneral: View { .tint(.accentColor) } - Section(header: Text("Network"), footer: Text("Try between some of the providers in case something is not loading if it should be, as it might be the fault of your ISP.")){ - HStack { - Text("DNS service") - Spacer() - Menu(customDNSProvider) { - ForEach(customDNSProviderList, id: \.self) { provider in - Button(action: { customDNSProvider = provider }) { - Text(provider) - } - } - } - } - if customDNSProvider == "Custom" { - TextField("Primary DNS", text: $customPrimaryDNS) - .keyboardType(.numbersAndPunctuation) - TextField("Secondary DNS", text: $customSecondaryDNS) - .keyboardType(.numbersAndPunctuation) - } - } - Section(header: Text("Advanced"), footer: Text("Anonymous data is collected to improve the app. No personal information is collected. This can be disabled at any time.")) { Toggle("Enable Analytics", isOn: $analyticsEnabled) .tint(.accentColor) diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index d9c74ca..ad75420 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -61,7 +61,6 @@ 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; - 1ED156202D949B1A00C11BCD /* CustomDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -120,7 +119,6 @@ 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; - 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNS.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -289,7 +287,6 @@ 13DB7CEA2D7DED50004371D3 /* DownloadManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, - 1ED1561E2D949AFB00C11BCD /* NetworkDNS */, 13DC0C442D302C6A00D0F966 /* MediaPlayer */, 133D7C862D2BE2640075467E /* Extensions */, 1327FBA52D758CEA00FC6689 /* Analytics */, @@ -440,14 +437,6 @@ path = Components; sourceTree = ""; }; - 1ED1561E2D949AFB00C11BCD /* NetworkDNS */ = { - isa = PBXGroup; - children = ( - 1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */, - ); - path = NetworkDNS; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -573,7 +562,6 @@ 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */, 13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */, 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */, - 1ED156202D949B1A00C11BCD /* CustomDNS.swift in Sources */, 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */, 1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */, 13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */,