mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
fuck the isp's 🗣️ 🔥 (#60)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run
This commit is contained in:
commit
8f3c855fe2
13 changed files with 373 additions and 166 deletions
|
|
@ -2,8 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>Sora may requires access to your device's camera.</string>
|
|
||||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ struct SoraApp: App {
|
||||||
@StateObject private var moduleManager = ModuleManager()
|
@StateObject private var moduleManager = ModuleManager()
|
||||||
@StateObject private var librarykManager = LibraryManager()
|
@StateObject private var librarykManager = LibraryManager()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
registerCustomDNSGlobally()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ extension JSContext {
|
||||||
request.setValue(value, forHTTPHeaderField: key)
|
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 {
|
if let error = error {
|
||||||
Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)", type: "Error")
|
||||||
reject.call(withArguments: [error.localizedDescription])
|
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 {
|
if let error = error {
|
||||||
Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("Network error in fetchV2NativeFunction: \(error.localizedDescription)", type: "Error")
|
||||||
reject.call(withArguments: [error.localizedDescription])
|
reject.call(withArguments: [error.localizedDescription])
|
||||||
|
|
|
||||||
|
|
@ -5,55 +5,9 @@
|
||||||
// Created by Francesco on 05/01/25.
|
// Created by Francesco on 05/01/25.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Network
|
|
||||||
import Foundation
|
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 {
|
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 = [
|
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/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; 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"
|
"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]
|
userAgents.randomElement() ?? userAgents[0]
|
||||||
}
|
}()
|
||||||
|
|
||||||
static var custom: URLSession {
|
static let custom: URLSession = {
|
||||||
let configuration = URLSessionConfiguration.default
|
let configuration = URLSessionConfiguration.default
|
||||||
configuration.httpAdditionalHeaders = [
|
configuration.httpAdditionalHeaders = ["User-Agent": randomUserAgent]
|
||||||
"User-Agent": randomUserAgent
|
|
||||||
]
|
|
||||||
return URLSession(configuration: configuration)
|
return URLSession(configuration: configuration)
|
||||||
}
|
}()
|
||||||
|
|
||||||
static var cloudflareCustom: URLSession {
|
static let customDNS: URLSession = {
|
||||||
let configuration = URLSessionConfiguration.default
|
let config = URLSessionConfiguration.default
|
||||||
configuration.httpAdditionalHeaders = [
|
var protocols = config.protocolClasses ?? []
|
||||||
"User-Agent": randomUserAgent
|
protocols.insert(CustomURLProtocol.self, at: 0)
|
||||||
]
|
config.protocolClasses = protocols
|
||||||
|
return URLSession(configuration: config, delegate: InsecureSessionDelegate(), delegateQueue: nil)
|
||||||
let dnsServers = currentDNSProvider.servers
|
}()
|
||||||
|
|
||||||
let dnsSettings: [AnyHashable: Any] = [
|
|
||||||
"DNSSettings": [
|
|
||||||
"ServerAddresses": dnsServers
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
configuration.connectionProxyDictionary = dnsSettings
|
|
||||||
return URLSession(configuration: configuration)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class JSController: ObservableObject {
|
||||||
return
|
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 }
|
guard let self = self else { return }
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
|
@ -77,7 +77,7 @@ class JSController: ObservableObject {
|
||||||
return
|
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 }
|
guard let self = self else { return }
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
|
@ -130,7 +130,7 @@ class JSController: ObservableObject {
|
||||||
return
|
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 }
|
guard let self = self else { return }
|
||||||
|
|
||||||
if let error = error {
|
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) {
|
func fetchStreamUrlJSSecond(episodeUrl: String, softsub: Bool = false, completion: @escaping ((stream: String?, subtitles: String?)) -> Void) {
|
||||||
let url = URL(string: episodeUrl)!
|
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 {
|
if let error = error {
|
||||||
Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error")
|
Logger.shared.log("URLSession error: \(error.localizedDescription)", type: "Error")
|
||||||
DispatchQueue.main.async { completion((nil, nil)) }
|
DispatchQueue.main.async { completion((nil, nil)) }
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class VTTSubtitlesLoader: ObservableObject {
|
||||||
|
|
||||||
let format = determineSubtitleFormat(from: url)
|
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,
|
guard let data = data,
|
||||||
let content = String(data: data, encoding: .utf8),
|
let content = String(data: data, encoding: .utf8),
|
||||||
error == nil else { return }
|
error == nil else { return }
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ struct ModuleAdditionSettingsView: View {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
do {
|
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)
|
let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: data)
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.moduleMetadata = metadata
|
self.moduleMetadata = metadata
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@ class ModuleManager: ObservableObject {
|
||||||
throw NSError(domain: "Module already exists", code: -1)
|
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)
|
let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData)
|
||||||
|
|
||||||
guard let scriptUrl = URL(string: metadata.scriptUrl) else {
|
guard let scriptUrl = URL(string: metadata.scriptUrl) else {
|
||||||
throw NSError(domain: "Invalid script URL", code: -1)
|
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 {
|
guard let jsContent = String(data: scriptData, encoding: .utf8) else {
|
||||||
throw NSError(domain: "Invalid script encoding", code: -1)
|
throw NSError(domain: "Invalid script encoding", code: -1)
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ class ModuleManager: ObservableObject {
|
||||||
func refreshModules() async {
|
func refreshModules() async {
|
||||||
for (index, module) in modules.enumerated() {
|
for (index, module) in modules.enumerated() {
|
||||||
do {
|
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)
|
let newMetadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData)
|
||||||
|
|
||||||
if newMetadata.version != module.metadata.version {
|
if newMetadata.version != module.metadata.version {
|
||||||
|
|
@ -102,7 +102,7 @@ class ModuleManager: ObservableObject {
|
||||||
throw NSError(domain: "Invalid script URL", code: -1)
|
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 {
|
guard let jsContent = String(data: scriptData, encoding: .utf8) else {
|
||||||
throw NSError(domain: "Invalid script encoding", code: -1)
|
throw NSError(domain: "Invalid script encoding", code: -1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
267
Sora/Utils/NetworkDNS/CustomDNS.swift
Normal file
267
Sora/Utils/NetworkDNS/CustomDNS.swift
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
//
|
||||||
|
// 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 {
|
||||||
|
// 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 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] {
|
||||||
|
// Existing implementation remains unchanged.
|
||||||
|
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..<qdCount {
|
||||||
|
while offset < data.count && data[offset] != 0 {
|
||||||
|
let length = Int(data[offset])
|
||||||
|
offset += 1 + length
|
||||||
|
}
|
||||||
|
offset += 1
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
for _ in 0..<anCount {
|
||||||
|
if offset < data.count {
|
||||||
|
let nameByte = data[offset]
|
||||||
|
if nameByte & 0xC0 == 0xC0 {
|
||||||
|
offset += 2
|
||||||
|
} else {
|
||||||
|
while offset < data.count && data[offset] != 0 {
|
||||||
|
let length = Int(data[offset])
|
||||||
|
offset += 1 + length
|
||||||
|
}
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let type = readUInt16(), let _ = readUInt16() else { break }
|
||||||
|
let _ = readUInt32()
|
||||||
|
guard let dataLen = readUInt16() else { break }
|
||||||
|
if type == 1 && dataLen == 4 {
|
||||||
|
guard offset + 4 <= data.count else { break }
|
||||||
|
let ipBytes = data[offset..<offset+4]
|
||||||
|
let ip = ipBytes.map { String($0) }.joined(separator: ".")
|
||||||
|
ips.append(ip)
|
||||||
|
}
|
||||||
|
offset += Int(dataLen)
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(host: String, completion: @escaping (Result<[String], Error>) -> 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
|
||||||
|
}
|
||||||
|
|
@ -296,7 +296,7 @@ struct MediaInfoView: View {
|
||||||
UserDefaults.standard.set(99999999.0, forKey: "totalTime_\(href)")
|
UserDefaults.standard.set(99999999.0, forKey: "totalTime_\(href)")
|
||||||
}
|
}
|
||||||
refreshTrigger.toggle()
|
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)
|
.id(refreshTrigger)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@ struct SettingsViewGeneral: View {
|
||||||
@AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false
|
@AppStorage("multiThreads") private var multiThreadsEnabled: Bool = false
|
||||||
@AppStorage("metadataProviders") private var metadataProviders: String = "AniList"
|
@AppStorage("metadataProviders") private var metadataProviders: String = "AniList"
|
||||||
@AppStorage("CustomDNSProvider") private var customDNSProvider: String = "Cloudflare"
|
@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("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
|
||||||
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
|
@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"]
|
private let metadataProvidersList = ["AniList"]
|
||||||
@EnvironmentObject var settings: Settings
|
@EnvironmentObject var settings: Settings
|
||||||
|
|
||||||
|
|
@ -26,7 +28,7 @@ struct SettingsViewGeneral: View {
|
||||||
Form {
|
Form {
|
||||||
Section(header: Text("Interface")) {
|
Section(header: Text("Interface")) {
|
||||||
ColorPicker("Accent Color", selection: $settings.accentColor)
|
ColorPicker("Accent Color", selection: $settings.accentColor)
|
||||||
HStack() {
|
HStack {
|
||||||
Text("Appearance")
|
Text("Appearance")
|
||||||
Picker("Appearance", selection: $settings.selectedAppearance) {
|
Picker("Appearance", selection: $settings.selectedAppearance) {
|
||||||
Text("System").tag(Appearance.system)
|
Text("System").tag(Appearance.system)
|
||||||
|
|
@ -42,18 +44,10 @@ struct SettingsViewGeneral: View {
|
||||||
Text("Episodes Range")
|
Text("Episodes Range")
|
||||||
Spacer()
|
Spacer()
|
||||||
Menu {
|
Menu {
|
||||||
Button(action: { episodeChunkSize = 25 }) {
|
Button(action: { episodeChunkSize = 25 }) { Text("25") }
|
||||||
Text("25")
|
Button(action: { episodeChunkSize = 50 }) { Text("50") }
|
||||||
}
|
Button(action: { episodeChunkSize = 75 }) { Text("75") }
|
||||||
Button(action: { episodeChunkSize = 50 }) {
|
Button(action: { episodeChunkSize = 100 }) { Text("100") }
|
||||||
Text("50")
|
|
||||||
}
|
|
||||||
Button(action: { episodeChunkSize = 75 }) {
|
|
||||||
Text("75")
|
|
||||||
}
|
|
||||||
Button(action: { episodeChunkSize = 100 }) {
|
|
||||||
Text("100")
|
|
||||||
}
|
|
||||||
} label: {
|
} label: {
|
||||||
Text("\(episodeChunkSize)")
|
Text("\(episodeChunkSize)")
|
||||||
}
|
}
|
||||||
|
|
@ -65,9 +59,7 @@ struct SettingsViewGeneral: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
Menu(metadataProviders) {
|
Menu(metadataProviders) {
|
||||||
ForEach(metadataProvidersList, id: \.self) { provider in
|
ForEach(metadataProvidersList, id: \.self) { provider in
|
||||||
Button(action: {
|
Button(action: { metadataProviders = provider }) {
|
||||||
metadataProviders = provider
|
|
||||||
}) {
|
|
||||||
Text(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.")) {
|
Section(header: Text("Media Grid Layout"), footer: Text("Adjust the number of media items per row in portrait and landscape modes.")) {
|
||||||
HStack {
|
HStack {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
Picker("Portrait Columns", selection: $mediaColumnsPortrait) {
|
Picker("Portrait Columns", selection: $mediaColumnsPortrait) {
|
||||||
ForEach(1..<6) { i in
|
ForEach(1..<6) { i in Text("\(i)").tag(i) }
|
||||||
Text("\(i)").tag(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pickerStyle(MenuPickerStyle())
|
.pickerStyle(MenuPickerStyle())
|
||||||
} else {
|
} else {
|
||||||
Picker("Portrait Columns", selection: $mediaColumnsPortrait) {
|
Picker("Portrait Columns", selection: $mediaColumnsPortrait) {
|
||||||
ForEach(1..<5) { i in
|
ForEach(1..<5) { i in Text("\(i)").tag(i) }
|
||||||
Text("\(i)").tag(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pickerStyle(MenuPickerStyle())
|
.pickerStyle(MenuPickerStyle())
|
||||||
}
|
}
|
||||||
|
|
@ -101,16 +84,12 @@ struct SettingsViewGeneral: View {
|
||||||
HStack {
|
HStack {
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
Picker("Landscape Columns", selection: $mediaColumnsLandscape) {
|
Picker("Landscape Columns", selection: $mediaColumnsLandscape) {
|
||||||
ForEach(2..<9) { i in
|
ForEach(2..<9) { i in Text("\(i)").tag(i) }
|
||||||
Text("\(i)").tag(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pickerStyle(MenuPickerStyle())
|
.pickerStyle(MenuPickerStyle())
|
||||||
} else {
|
} else {
|
||||||
Picker("Landscape Columns", selection: $mediaColumnsLandscape) {
|
Picker("Landscape Columns", selection: $mediaColumnsLandscape) {
|
||||||
ForEach(2..<6) { i in
|
ForEach(2..<6) { i in Text("\(i)").tag(i) }
|
||||||
Text("\(i)").tag(i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.pickerStyle(MenuPickerStyle())
|
.pickerStyle(MenuPickerStyle())
|
||||||
}
|
}
|
||||||
|
|
@ -122,23 +101,29 @@ struct SettingsViewGeneral: View {
|
||||||
.tint(.accentColor)
|
.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("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.")){
|
||||||
Toggle("Enable Analytics", isOn: $analyticsEnabled)
|
|
||||||
.tint(.accentColor)
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("DNS service")
|
Text("DNS service")
|
||||||
Spacer()
|
Spacer()
|
||||||
Menu(customDNSProvider) {
|
Menu(customDNSProvider) {
|
||||||
ForEach(customDNSProviderList, id: \.self) { provider in
|
ForEach(customDNSProviderList, id: \.self) { provider in
|
||||||
Button(action: {
|
Button(action: { customDNSProvider = provider }) {
|
||||||
customDNSProvider = provider
|
|
||||||
}) {
|
|
||||||
Text(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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("General")
|
.navigationTitle("General")
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; };
|
13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; };
|
||||||
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; };
|
1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; };
|
||||||
1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.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 */; };
|
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
|
@ -119,6 +120,7 @@
|
||||||
13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = "<group>"; };
|
13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = "<group>"; };
|
||||||
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = "<group>"; };
|
1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = "<group>"; };
|
||||||
1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = "<group>"; };
|
1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = "<group>"; };
|
||||||
|
1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNS.swift; sourceTree = "<group>"; };
|
||||||
73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = "<group>"; };
|
73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
|
@ -284,6 +286,7 @@
|
||||||
133D7C852D2BE2640075467E /* Utils */ = {
|
133D7C852D2BE2640075467E /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
1ED1561E2D949AFB00C11BCD /* NetworkDNS */,
|
||||||
13DB7CEA2D7DED50004371D3 /* DownloadManager */,
|
13DB7CEA2D7DED50004371D3 /* DownloadManager */,
|
||||||
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */,
|
13C0E5E82D5F85DD00E7F619 /* ContinueWatching */,
|
||||||
13103E8C2D58E037000F0673 /* SkeletonCells */,
|
13103E8C2D58E037000F0673 /* SkeletonCells */,
|
||||||
|
|
@ -437,6 +440,14 @@
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1ED1561E2D949AFB00C11BCD /* NetworkDNS */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1ED1561F2D949B1A00C11BCD /* CustomDNS.swift */,
|
||||||
|
);
|
||||||
|
path = NetworkDNS;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
|
@ -562,6 +573,7 @@
|
||||||
1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */,
|
1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */,
|
||||||
13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */,
|
13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */,
|
||||||
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */,
|
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */,
|
||||||
|
1ED156202D949B1A00C11BCD /* CustomDNS.swift in Sources */,
|
||||||
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
||||||
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
||||||
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */,
|
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,42 @@
|
||||||
{
|
{
|
||||||
"object": {
|
"originHash" : "28f2c123747ea3d0aee96430294eb72e7254bafd504c83303b2a2e02f270f26f",
|
||||||
"pins": [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"package": "Drops",
|
"identity" : "drops",
|
||||||
"repositoryURL": "https://github.com/omaralbeik/Drops.git",
|
"kind" : "remoteSourceControl",
|
||||||
"state": {
|
"location" : "https://github.com/omaralbeik/Drops.git",
|
||||||
"branch": "main",
|
"state" : {
|
||||||
"revision": "5824681795286c36bdc4a493081a63e64e2a064e",
|
"branch" : "main",
|
||||||
"version": null
|
"revision" : "5824681795286c36bdc4a493081a63e64e2a064e"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue