mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
what i've done so far
This commit is contained in:
1 parent
727fd664ef
commit
92e6cc7900
47 files changed
+298
-271
No files matched your search
@@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
extension CharacterSet {
|
||||
static var urlQueryAllowedStrict: CharacterSet {
|
||||
var allowed = CharacterSet.urlQueryAllowed
|
||||
allowed.remove(charactersIn: "/?@&=+$")
|
||||
return allowed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
extension Collection {
|
||||
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> SubSequence {
|
||||
guard let index = try indices.reversed().first(where: { try !predicate(self[$0]) }) else {
|
||||
return self[startIndex..<startIndex]
|
||||
}
|
||||
return self[...index]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
extension Color {
|
||||
init(hex: String) {
|
||||
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
var int: UInt64 = 0
|
||||
Scanner(string: hex).scanHexInt64(&int)
|
||||
let a, r, g, b: UInt64
|
||||
switch hex.count {
|
||||
case 3:
|
||||
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
|
||||
case 6:
|
||||
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
|
||||
case 8:
|
||||
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
|
||||
default:
|
||||
(a, r, g, b) = (1, 1, 1, 0)
|
||||
}
|
||||
|
||||
self.init(
|
||||
.sRGB,
|
||||
red: Double(r) / 255,
|
||||
green: Double(g) / 255,
|
||||
blue: Double(b) / 255,
|
||||
opacity: Double(a) / 255
|
||||
)
|
||||
}
|
||||
|
||||
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
|
||||
var r: CGFloat = 0
|
||||
var g: CGFloat = 0
|
||||
var b: CGFloat = 0
|
||||
var a: CGFloat = 0
|
||||
|
||||
guard UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a) else {
|
||||
return (0, 0, 0, 0)
|
||||
}
|
||||
|
||||
return (r, g, b, a)
|
||||
}
|
||||
|
||||
func lighter(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).lighter(by: amount)) }
|
||||
func darker(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).darker(by: amount)) }
|
||||
|
||||
var brightness: CGFloat {
|
||||
(
|
||||
components.red * 299
|
||||
+ components.green * 587
|
||||
+ components.blue * 114
|
||||
) / 1000
|
||||
}
|
||||
|
||||
func normalized(_ by: CGFloat) -> Color {
|
||||
brightness < 0.5
|
||||
? self.lighter(by: max(by - brightness, 0))
|
||||
: self.darker(by: max(brightness - by, 0))
|
||||
}
|
||||
|
||||
var hexString: String {
|
||||
String(
|
||||
format: "%02X%02X%02X",
|
||||
Int(components.red * 255),
|
||||
Int(components.green * 255),
|
||||
Int(components.blue * 255)
|
||||
)
|
||||
}
|
||||
|
||||
var uInt32: UInt32 {
|
||||
UInt32(components.alpha * 255) << 24
|
||||
| UInt32(components.red * 255) << 16
|
||||
| UInt32(components.green * 255) << 8
|
||||
| UInt32(components.blue * 255)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
static func random(_ length: Int) -> Data {
|
||||
return Data((0 ..< length).map { _ in UInt8.random(in: UInt8.min ... UInt8.max) })
|
||||
}
|
||||
|
||||
var hexEncodedString: String {
|
||||
map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
|
||||
static var musixmatchTokenPlaceholder: String {
|
||||
"2" + self.random(53).hexEncodedString
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
extension Dictionary {
|
||||
var queryString: String {
|
||||
return self
|
||||
.compactMap({ (key, value) -> String? in
|
||||
guard let keyString = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowedStrict),
|
||||
let valueString = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowedStrict) else {
|
||||
return nil
|
||||
}
|
||||
return "\(keyString)=\(valueString)"
|
||||
})
|
||||
.joined(separator: "&")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension Locale {
|
||||
static func isInRegion(_ regionCode: String, orHasLanguage languageCode: String) -> Bool {
|
||||
self.current.regionCode == regionCode || self.preferredLanguages.contains(languageCode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import NaturalLanguage
|
||||
|
||||
extension String {
|
||||
static func ~= (lhs: String, rhs: String) -> Bool {
|
||||
lhs.firstMatch(rhs) != nil
|
||||
}
|
||||
|
||||
var localized: String {
|
||||
BundleHelper.shared.localizedString(self)
|
||||
}
|
||||
|
||||
var uiKitLocalized: String {
|
||||
let bundle = Bundle(for: UIApplication.self)
|
||||
return bundle.localizedString(forKey: self, value: nil, table: nil)
|
||||
}
|
||||
|
||||
func localizeWithFormat(_ arguments: CVarArg...) -> String {
|
||||
String(format: self.localized, arguments: arguments)
|
||||
}
|
||||
|
||||
var range: NSRange {
|
||||
NSRange(self.startIndex..., in: self)
|
||||
}
|
||||
|
||||
var strippedTrackTitle: String {
|
||||
String(
|
||||
self
|
||||
.removeMatches("\\(.*\\)")
|
||||
.removeMatches("- .*")
|
||||
.trimmingCharacters(in: .whitespaces)
|
||||
)
|
||||
}
|
||||
|
||||
var isHex: Bool {
|
||||
self ~= "^[a-f0-9]+$"
|
||||
}
|
||||
|
||||
var lyricsNoteIfEmpty: String {
|
||||
self.isEmpty ? "♪" : self
|
||||
}
|
||||
|
||||
func containsInsensitive<S: StringProtocol>(_ s: S) -> Bool {
|
||||
self.range(of: s, options: .caseInsensitive) != nil
|
||||
}
|
||||
|
||||
func firstMatch(_ pattern: String) -> NSTextCheckingResult? {
|
||||
try! NSRegularExpression(pattern: pattern)
|
||||
.firstMatch(in: self, range: self.range)
|
||||
}
|
||||
|
||||
func removeMatches(_ pattern: String) -> String {
|
||||
try! NSRegularExpression(pattern: pattern)
|
||||
.stringByReplacingMatches(
|
||||
in: self,
|
||||
range: self.range,
|
||||
withTemplate: ""
|
||||
)
|
||||
}
|
||||
|
||||
var isCanBeRomanizedLanguage: Bool {
|
||||
["ja", "ko", "z1"].contains(self) || self.contains("zh")
|
||||
}
|
||||
|
||||
var hexadecimal: Data? {
|
||||
var data = Data(capacity: count / 2)
|
||||
|
||||
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
|
||||
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
|
||||
let byteString = (self as NSString).substring(with: match!.range)
|
||||
let num = UInt8(byteString, radix: 16)!
|
||||
data.append(num)
|
||||
}
|
||||
|
||||
guard data.count > 0 else { return nil }
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import NaturalLanguage
|
||||
|
||||
extension Array where Element == String {
|
||||
var canBeRomanized: Bool {
|
||||
var languageList: [NLLanguage] = []
|
||||
|
||||
for line in self {
|
||||
if let language = NLLanguageRecognizer.dominantLanguage(for: line) {
|
||||
languageList.append(language)
|
||||
}
|
||||
}
|
||||
|
||||
let canBeRomanizedLanguageCount = languageList.filter {
|
||||
[.japanese, .korean, .simplifiedChinese, .traditionalChinese].contains($0)
|
||||
}.count
|
||||
|
||||
return Double(canBeRomanizedLanguageCount) / Double(languageList.count) > 0.15
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
func mix(with target: UIColor, amount: CGFloat) -> Self {
|
||||
var r1: CGFloat = 0, g1: CGFloat = 0, b1: CGFloat = 0, a1: CGFloat = 0
|
||||
var r2: CGFloat = 0, g2: CGFloat = 0, b2: CGFloat = 0, a2: CGFloat = 0
|
||||
|
||||
self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
|
||||
target.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)
|
||||
|
||||
return Self(
|
||||
red: r1 * (1.0 - amount) + r2 * amount,
|
||||
green: g1 * (1.0 - amount) + g2 * amount,
|
||||
blue: b1 * (1.0 - amount) + b2 * amount,
|
||||
alpha: a1 * (1.0 - amount) + a2 * amount
|
||||
)
|
||||
}
|
||||
|
||||
func lighter(by amount: CGFloat = 0.2) -> Self { mix(with: .white, amount: amount) }
|
||||
func darker(by amount: CGFloat = 0.2) -> Self { mix(with: .black, amount: amount) }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import UIKit
|
||||
|
||||
extension UIDevice {
|
||||
var isIpad: Bool {
|
||||
self.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
var musixmatchAppId: String {
|
||||
UIDevice.current.isIpad
|
||||
? "mac-ios-ipad-v1.0"
|
||||
: "mac-ios-v2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
func hasParent(matching regex: String) -> Bool {
|
||||
guard let parent = self.superview else {
|
||||
return false
|
||||
}
|
||||
|
||||
let parentClassName = NSStringFromClass(type(of: parent))
|
||||
if parentClassName ~= regex {
|
||||
return true
|
||||
} else {
|
||||
return parent.hasParent(matching: regex)
|
||||
}
|
||||
}
|
||||
|
||||
func subviews(matching regex: String) -> [UIView] {
|
||||
var matchingSubviews = [UIView]()
|
||||
var stack = [self]
|
||||
|
||||
while !stack.isEmpty {
|
||||
let currentView = stack.removeLast()
|
||||
|
||||
for subview in currentView.subviews {
|
||||
if NSStringFromClass(type(of: subview)) ~= regex {
|
||||
matchingSubviews.append(subview)
|
||||
}
|
||||
stack.append(subview)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingSubviews
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
var isLyrics: Bool {
|
||||
self.path.contains("color-lyrics/v2")
|
||||
}
|
||||
|
||||
var isOpenSpotifySafariExtension: Bool {
|
||||
self.host == "eevee"
|
||||
}
|
||||
|
||||
var isCustomize: Bool {
|
||||
self.path.contains("v1/customize")
|
||||
}
|
||||
|
||||
var isBootstrap: Bool {
|
||||
self.path.contains("v1/bootstrap")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
private static let defaults = UserDefaults.standard
|
||||
|
||||
private static let lyricsSourceKey = "lyricsSource"
|
||||
private static let musixmatchTokenKey = "musixmatchToken"
|
||||
private static let darkPopUpsKey = "darkPopUps"
|
||||
private static let patchTypeKey = "patchType"
|
||||
private static let overwriteConfigurationKey = "overwriteConfiguration"
|
||||
private static let lyricsColorsKey = "lyricsColors"
|
||||
private static let lyricsOptionsKey = "lyricsOptions"
|
||||
private static let hasShownCommonIssuesTipKey = "hasShownCommonIssuesTip"
|
||||
|
||||
static var lyricsSource: LyricsSource {
|
||||
get {
|
||||
if let rawValue = defaults.object(forKey: lyricsSourceKey) as? Int {
|
||||
return LyricsSource(rawValue: rawValue)!
|
||||
}
|
||||
|
||||
return LyricsSource.defaultSource
|
||||
}
|
||||
set (newSource) {
|
||||
defaults.set(newSource.rawValue, forKey: lyricsSourceKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var musixmatchToken: String {
|
||||
get {
|
||||
defaults.string(forKey: musixmatchTokenKey) ?? ""
|
||||
}
|
||||
set (token) {
|
||||
defaults.set(token, forKey: musixmatchTokenKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var lyricsOptions: LyricsOptions {
|
||||
get {
|
||||
if let data = defaults.object(forKey: lyricsOptionsKey) as? Data,
|
||||
let lyricsOptions = try? JSONDecoder().decode(LyricsOptions.self, from: data) {
|
||||
return lyricsOptions
|
||||
}
|
||||
|
||||
return LyricsOptions(
|
||||
romanization: false,
|
||||
musixmatchLanguage: Locale.current.languageCode ?? "",
|
||||
lrclibUrl: LrclibLyricsRepository.originalApiUrl,
|
||||
geniusFallback: true,
|
||||
showFallbackReasons: true
|
||||
)
|
||||
}
|
||||
set (lyricsOptions) {
|
||||
defaults.set(try! JSONEncoder().encode(lyricsOptions), forKey: lyricsOptionsKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var darkPopUps: Bool {
|
||||
get {
|
||||
defaults.object(forKey: darkPopUpsKey) as? Bool ?? true
|
||||
}
|
||||
set (darkPopUps) {
|
||||
defaults.set(darkPopUps, forKey: darkPopUpsKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var patchType: PatchType {
|
||||
get {
|
||||
if let rawValue = defaults.object(forKey: patchTypeKey) as? Int {
|
||||
return PatchType(rawValue: rawValue) ?? .requests
|
||||
}
|
||||
|
||||
return .notSet
|
||||
}
|
||||
set (patchType) {
|
||||
defaults.set(patchType.rawValue, forKey: patchTypeKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var overwriteConfiguration: Bool {
|
||||
get {
|
||||
defaults.bool(forKey: overwriteConfigurationKey)
|
||||
}
|
||||
set (overwriteConfiguration) {
|
||||
defaults.set(overwriteConfiguration, forKey: overwriteConfigurationKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var hasShownCommonIssuesTip: Bool {
|
||||
get {
|
||||
defaults.bool(forKey: hasShownCommonIssuesTipKey)
|
||||
}
|
||||
set (hasShownCommonIssuesTip) {
|
||||
defaults.set(hasShownCommonIssuesTip, forKey: hasShownCommonIssuesTipKey)
|
||||
}
|
||||
}
|
||||
|
||||
static var lyricsColors: LyricsColorsSettings {
|
||||
get {
|
||||
if let data = defaults.object(forKey: lyricsColorsKey) as? Data {
|
||||
return try! JSONDecoder().decode(LyricsColorsSettings.self, from: data)
|
||||
}
|
||||
|
||||
return LyricsColorsSettings(
|
||||
displayOriginalColors: true,
|
||||
useStaticColor: false,
|
||||
staticColor: "",
|
||||
normalizationFactor: 0.5
|
||||
)
|
||||
}
|
||||
set (lyricsColors) {
|
||||
defaults.set(try! JSONEncoder().encode(lyricsColors), forKey: lyricsColorsKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user