128 lines
3.5 KiB
Swift
128 lines
3.5 KiB
Swift
//
|
|
// Settings.swift
|
|
// App
|
|
//
|
|
// Created by James Magahern on 3/9/21.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
@propertyWrapper
|
|
public struct SettingProperty<T: RawRepresentable>
|
|
{
|
|
public var key: String
|
|
public var defaultValue: T
|
|
public init(wrappedValue: T, key: String) {
|
|
self.key = key
|
|
self.defaultValue = wrappedValue
|
|
}
|
|
|
|
public var wrappedValue: T {
|
|
get {
|
|
guard let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue else { return defaultValue }
|
|
return T(rawValue: rawValue) ?? defaultValue
|
|
}
|
|
set {
|
|
UserDefaults.standard.setValue(newValue.rawValue, forKey: key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// - These coercions into RawRepresentable are stupid. How do I write specializations for each type instead?
|
|
|
|
extension Dictionary: RawRepresentable where Key == String, Value == String {
|
|
public typealias RawValue = [String: String]
|
|
|
|
public init?(rawValue: [String : String]) {
|
|
self.init()
|
|
self.merge(rawValue, uniquingKeysWith: { $1 })
|
|
}
|
|
|
|
public var rawValue: [String : String] {
|
|
return self
|
|
}
|
|
}
|
|
|
|
extension Optional: RawRepresentable where Wrapped == String {
|
|
public typealias RawValue = String?
|
|
|
|
public init?(rawValue: String?) {
|
|
if let rawValue {
|
|
self = String(rawValue: rawValue)
|
|
} else {
|
|
self = .none
|
|
}
|
|
}
|
|
|
|
public var rawValue: String? {
|
|
return self
|
|
}
|
|
}
|
|
|
|
extension String: RawRepresentable {
|
|
public typealias RawValue = String
|
|
|
|
public init?(rawValue: String) {
|
|
self.init(rawValue)
|
|
}
|
|
|
|
public var rawValue: String {
|
|
return self
|
|
}
|
|
}
|
|
|
|
class Settings
|
|
{
|
|
static let shared = Settings()
|
|
|
|
public enum SearchProviderSetting: String, CaseIterable {
|
|
case google = "Google"
|
|
case duckduckgo = "DuckDuckGo"
|
|
case searxnor = "Searx.nor"
|
|
case whoogle = "Whoogle.nor"
|
|
|
|
func provider() -> SearchProvider {
|
|
switch self {
|
|
case .google: return SearchProvider.google
|
|
case .duckduckgo: return SearchProvider.duckduckgo
|
|
case .searxnor: return SearchProvider.searxnor
|
|
case .whoogle: return SearchProvider.whoogle
|
|
}
|
|
}
|
|
}
|
|
|
|
@SettingProperty(key: "searchProvider")
|
|
public var searchProvider: SearchProviderSetting = .searxnor
|
|
|
|
@SettingProperty(key: "redirectRules")
|
|
public var redirectRules: [String: String] = [:]
|
|
|
|
func redirectRule(for url: URL) -> URL? {
|
|
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false), var host = url.host {
|
|
// Remove "www." if necessary.
|
|
host = host.replacingOccurrences(of: "www.", with: "")
|
|
|
|
if let alternateHost = redirectRules[host] {
|
|
components.host = alternateHost
|
|
|
|
// Since redirected hosts are usually self-hosted.
|
|
// If https is required, it will be upgraded anyway.
|
|
components.scheme = "http"
|
|
|
|
return components.url
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
@SettingProperty(key: "userScript")
|
|
public var userScript: String = ""
|
|
|
|
@SettingProperty(key: "userStylesheet")
|
|
public var userStylesheet: String = ""
|
|
|
|
@SettingProperty(key: "syncServer")
|
|
public var syncServer: Optional<String> = "https://attractor.severnaya.net"
|
|
}
|