mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
better URLSession intercepts/fix #182, lyrics colors options
This commit is contained in:
@@ -5,17 +5,71 @@ class SPTDataLoaderServiceHook: ClassHook<NSObject> {
|
||||
|
||||
static let targetName = "SPTDataLoaderService"
|
||||
|
||||
// orion:new
|
||||
func shouldModify(_ url: URL) -> Bool {
|
||||
let isModifyingCustomizeResponse = UserDefaults.patchType == .requests
|
||||
return url.isLyrics || (url.isCustomize && isModifyingCustomizeResponse)
|
||||
}
|
||||
|
||||
func URLSession(
|
||||
_ session: URLSession,
|
||||
task: URLSessionDataTask,
|
||||
didCompleteWithError error: Error?
|
||||
) {
|
||||
if let url = task.currentRequest?.url {
|
||||
if url.isLyrics || (UserDefaults.patchType == .requests && url.isCustomize) {
|
||||
return
|
||||
guard
|
||||
let request = task.currentRequest,
|
||||
let url = request.url
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
if error == nil && shouldModify(url) {
|
||||
|
||||
if let buffer = URLSessionHelper.shared.obtainData(for: url) {
|
||||
|
||||
if url.isLyrics {
|
||||
|
||||
do {
|
||||
orig.URLSession(
|
||||
session,
|
||||
dataTask: task,
|
||||
didReceiveData: try getCurrentTrackLyricsData(
|
||||
originalLyrics: try? Lyrics(serializedData: buffer)
|
||||
)
|
||||
)
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: nil)
|
||||
}
|
||||
catch {
|
||||
orig.URLSession(session, task: task, didCompleteWithError: error)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
var customizeMessage = try CustomizeMessage(serializedData: buffer)
|
||||
modifyRemoteConfiguration(&customizeMessage.response)
|
||||
|
||||
orig.URLSession(
|
||||
session,
|
||||
dataTask: task,
|
||||
didReceiveData: try customizeMessage.serializedData()
|
||||
)
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: nil)
|
||||
|
||||
NSLog("[EeveeSpotify] Modified customize data")
|
||||
return
|
||||
}
|
||||
catch {
|
||||
NSLog("[EeveeSpotify] Unable to modify customize data: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: error)
|
||||
|
||||
}
|
||||
|
||||
func URLSession(
|
||||
@@ -25,7 +79,7 @@ class SPTDataLoaderServiceHook: ClassHook<NSObject> {
|
||||
completionHandler handler: Any
|
||||
) {
|
||||
let url = response.url!
|
||||
|
||||
|
||||
if url.isLyrics, response.statusCode != 200 {
|
||||
|
||||
let okResponse = HTTPURLResponse(
|
||||
@@ -34,17 +88,17 @@ class SPTDataLoaderServiceHook: ClassHook<NSObject> {
|
||||
httpVersion: "2.0",
|
||||
headerFields: [:]
|
||||
)!
|
||||
|
||||
|
||||
do {
|
||||
let lyricsData = try getCurrentTrackLyricsData()
|
||||
|
||||
|
||||
orig.URLSession(
|
||||
session,
|
||||
dataTask: task,
|
||||
didReceiveResponse: okResponse,
|
||||
completionHandler: handler
|
||||
)
|
||||
|
||||
|
||||
orig.URLSession(session, dataTask: task, didReceiveData: lyricsData)
|
||||
orig.URLSession(session, task: task, didCompleteWithError: nil)
|
||||
|
||||
@@ -73,63 +127,14 @@ class SPTDataLoaderServiceHook: ClassHook<NSObject> {
|
||||
) {
|
||||
guard
|
||||
let request = task.currentRequest,
|
||||
let response = task.response,
|
||||
let url = request.url
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
if url.isLyrics {
|
||||
|
||||
do {
|
||||
orig.URLSession(
|
||||
session,
|
||||
dataTask: task,
|
||||
didReceiveData: try getCurrentTrackLyricsData(
|
||||
originalLyrics: try? Lyrics(serializedData: data)
|
||||
)
|
||||
)
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: nil)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
NSLog("[EeveeSpotify] Unable to load lyrics: \(error)")
|
||||
orig.URLSession(session, task: task, didCompleteWithError: error)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if url.isCustomize && UserDefaults.patchType == .requests {
|
||||
|
||||
do {
|
||||
guard let buffer = OfflineHelper.appendDataAndReturnIfFull(
|
||||
data,
|
||||
response: response
|
||||
) else {
|
||||
return
|
||||
}
|
||||
|
||||
OfflineHelper.dataBuffer = Data()
|
||||
|
||||
var customizeMessage = try CustomizeMessage(serializedData: buffer)
|
||||
modifyRemoteConfiguration(&customizeMessage.response)
|
||||
|
||||
orig.URLSession(
|
||||
session,
|
||||
dataTask: task,
|
||||
didReceiveData: try customizeMessage.serializedData()
|
||||
)
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: nil)
|
||||
|
||||
NSLog("[EeveeSpotify] Modified customize data")
|
||||
return
|
||||
}
|
||||
catch {
|
||||
NSLog("[EeveeSpotify] Unable to modify customize data: \(error)")
|
||||
}
|
||||
if shouldModify(url) {
|
||||
URLSessionHelper.shared.setOrAppend(data, for: url)
|
||||
return
|
||||
}
|
||||
|
||||
orig.URLSession(session, dataTask: task, didReceiveData: data)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import UIKit
|
||||
|
||||
class URLSessionHelper {
|
||||
|
||||
static let shared = URLSessionHelper()
|
||||
|
||||
private var requestsMap: [URL:Data]
|
||||
|
||||
private init() {
|
||||
self.requestsMap = [:]
|
||||
}
|
||||
|
||||
func setOrAppend(_ data: Data, for url: URL) {
|
||||
var loadedData = requestsMap[url] ?? Data()
|
||||
loadedData.append(data)
|
||||
|
||||
requestsMap[url] = loadedData
|
||||
}
|
||||
|
||||
func obtainData(for url: URL) -> Data? {
|
||||
return requestsMap.removeValue(forKey: url)
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ class SPTPlayerTrackHook: ClassHook<NSObject> {
|
||||
|
||||
static let targetName = "SPTPlayerTrack"
|
||||
|
||||
func setMetadata(_ metadata: [String:String]) {
|
||||
var meta = metadata
|
||||
|
||||
func metadata() -> [String:String] {
|
||||
var meta = orig.metadata()
|
||||
|
||||
meta["has_lyrics"] = "true"
|
||||
orig.setMetadata(meta)
|
||||
return meta
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,24 +29,24 @@ class EncoreButtonHook: ClassHook<UIButton> {
|
||||
}
|
||||
|
||||
func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data {
|
||||
|
||||
|
||||
guard let track = HookedInstances.currentTrack else {
|
||||
throw LyricsError.NoCurrentTrack
|
||||
}
|
||||
|
||||
|
||||
var source = UserDefaults.lyricsSource
|
||||
|
||||
|
||||
let plainLyrics: PlainLyrics?
|
||||
|
||||
|
||||
do {
|
||||
plainLyrics = try LyricsRepository.getLyrics(
|
||||
title: track.trackTitle(),
|
||||
artist: track.artistTitle(),
|
||||
title: track.trackTitle(),
|
||||
artist: track.artistTitle(),
|
||||
spotifyTrackId: track.URI().spt_trackIdentifier(),
|
||||
source: source
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
catch let error as LyricsError {
|
||||
|
||||
switch error {
|
||||
@@ -67,10 +67,10 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data {
|
||||
if source == .genius || !UserDefaults.geniusFallback {
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
NSLog("[EeveeSpotify] Unable to load lyrics from \(source): \(error), trying Genius as fallback")
|
||||
source = .genius
|
||||
|
||||
|
||||
plainLyrics = try LyricsRepository.getLyrics(
|
||||
title: track.trackTitle(),
|
||||
artist: track.artistTitle(),
|
||||
@@ -80,13 +80,28 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data {
|
||||
}
|
||||
|
||||
let lyrics = try Lyrics.with {
|
||||
$0.colors = originalLyrics?.colors ?? LyricsColors.with {
|
||||
$0.backgroundColor = Color(hex: track.extractedColorHex()).normalized.uInt32
|
||||
$0.lineColor = Color.black.uInt32
|
||||
$0.activeLineColor = Color.white.uInt32
|
||||
}
|
||||
$0.colors = getLyricsColors()
|
||||
$0.data = try LyricsHelper.composeLyricsData(plainLyrics!, source: source)
|
||||
}
|
||||
|
||||
return try lyrics.serializedData()
|
||||
|
||||
func getLyricsColors() -> LyricsColors {
|
||||
|
||||
let lyricsColorsSettings = UserDefaults.lyricsColors
|
||||
|
||||
if lyricsColorsSettings.displayOriginalColors, let originalLyrics = originalLyrics {
|
||||
return originalLyrics.colors
|
||||
}
|
||||
|
||||
return LyricsColors.with {
|
||||
$0.backgroundColor = lyricsColorsSettings.useStaticColor
|
||||
? Color(hex: lyricsColorsSettings.staticColor).uInt32
|
||||
: Color(hex: track.extractedColorHex())
|
||||
.normalized(lyricsColorsSettings.normalizationFactor)
|
||||
.uInt32
|
||||
$0.lineColor = Color.black.uInt32
|
||||
$0.activeLineColor = Color.white.uInt32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,6 @@ class LyricsHelper {
|
||||
captures[name] = String(line[substringRange])
|
||||
}
|
||||
}
|
||||
|
||||
if captures.count != 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let minute = Int(captures["minute"]!)!
|
||||
let seconds = Float(captures["seconds"]!)!
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
struct LyricsColorsSettings: Codable, Equatable {
|
||||
var displayOriginalColors: Bool
|
||||
var useStaticColor: Bool
|
||||
var staticColor: String
|
||||
var normalizationFactor: CGFloat
|
||||
}
|
||||
@@ -53,10 +53,19 @@ extension Color {
|
||||
) / 1000
|
||||
}
|
||||
|
||||
var normalized: Color {
|
||||
brightness < 0.5
|
||||
? self.lighter(by: 0.5 - brightness)
|
||||
: self.darker(by: brightness - 0.5)
|
||||
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 {
|
||||
|
||||
@@ -10,6 +10,7 @@ extension UserDefaults {
|
||||
private static let darkPopUpsKey = "darkPopUps"
|
||||
private static let patchTypeKey = "patchType"
|
||||
private static let overwriteConfigurationKey = "overwriteConfiguration"
|
||||
private static let lyricsColorsKey = "lyricsColors"
|
||||
|
||||
static var lyricsSource: LyricsSource {
|
||||
get {
|
||||
@@ -72,4 +73,22 @@ extension UserDefaults {
|
||||
defaults.set(overwriteConfiguration, forKey: overwriteConfigurationKey)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Orion
|
||||
|
||||
func showHavePremiumPopUp() {
|
||||
private func showHavePremiumPopUp() {
|
||||
PopUpHelper.showPopUp(
|
||||
delayed: true,
|
||||
message: "It looks like you have an active Premium subscription, so the tweak won't patch the data or restrict the use of Premium server-sided features. You can manage this in the EeveeSpotify settings.",
|
||||
@@ -8,18 +8,6 @@ func showHavePremiumPopUp() {
|
||||
)
|
||||
}
|
||||
|
||||
func showOfflineBnkMethodSetPopUp() {
|
||||
PopUpHelper.showPopUp(
|
||||
delayed: true,
|
||||
message: "App restart is needed to get Premium. You can manage the Premium patching method in the EeveeSpotify settings.",
|
||||
buttonText: "Restart Now",
|
||||
secondButtonText: "Restart Later",
|
||||
onPrimaryClick: {
|
||||
exitApplication()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
class SPTCoreURLSessionDataDelegateHook: ClassHook<NSObject> {
|
||||
|
||||
static let targetName = "SPTCoreURLSessionDataDelegate"
|
||||
@@ -29,37 +17,18 @@ class SPTCoreURLSessionDataDelegateHook: ClassHook<NSObject> {
|
||||
task: URLSessionDataTask,
|
||||
didCompleteWithError error: Error?
|
||||
) {
|
||||
if let url = task.currentRequest?.url, UserDefaults.patchType == .requests && url.isBootstrap {
|
||||
return
|
||||
}
|
||||
orig.URLSession(session, task: task, didCompleteWithError: error)
|
||||
}
|
||||
|
||||
func URLSession(
|
||||
_ session: URLSession,
|
||||
dataTask task: URLSessionDataTask,
|
||||
didReceiveData data: Data
|
||||
) {
|
||||
guard
|
||||
guard
|
||||
let request = task.currentRequest,
|
||||
let response = task.response,
|
||||
let url = request.url
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
if url.isBootstrap {
|
||||
|
||||
|
||||
if error == nil && url.isBootstrap {
|
||||
|
||||
let buffer = URLSessionHelper.shared.obtainData(for: url)!
|
||||
|
||||
do {
|
||||
guard let buffer = OfflineHelper.appendDataAndReturnIfFull(
|
||||
data,
|
||||
response: response
|
||||
) else {
|
||||
return
|
||||
}
|
||||
|
||||
OfflineHelper.dataBuffer = Data()
|
||||
|
||||
var bootstrapMessage = try BootstrapMessage(serializedData: buffer)
|
||||
|
||||
if UserDefaults.patchType == .notSet {
|
||||
@@ -98,6 +67,28 @@ class SPTCoreURLSessionDataDelegateHook: ClassHook<NSObject> {
|
||||
NSLog("[EeveeSpotify] Unable to modify bootstrap data: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
orig.URLSession(session, task: task, didCompleteWithError: error)
|
||||
}
|
||||
|
||||
func URLSession(
|
||||
_ session: URLSession,
|
||||
dataTask task: URLSessionDataTask,
|
||||
didReceiveData data: Data
|
||||
) {
|
||||
guard
|
||||
let request = task.currentRequest,
|
||||
let url = request.url
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let isModifyingBootstrapResponse = UserDefaults.patchType == .requests
|
||||
|
||||
if url.isBootstrap && isModifyingBootstrapResponse {
|
||||
URLSessionHelper.shared.setOrAppend(data, for: url)
|
||||
return
|
||||
}
|
||||
|
||||
orig.URLSession(session, dataTask: task, didReceiveData: data)
|
||||
}
|
||||
|
||||
@@ -26,21 +26,6 @@ class OfflineHelper {
|
||||
get throws { try Data(contentsOf: eeveeBnkPath) }
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
static var dataBuffer = Data()
|
||||
|
||||
static func appendDataAndReturnIfFull(_ data: Data, response: URLResponse) -> Data? {
|
||||
|
||||
dataBuffer.append(data)
|
||||
|
||||
if dataBuffer.count == response.expectedContentLength {
|
||||
return dataBuffer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private static func writeOfflineBnkData(_ data: Data) throws {
|
||||
|
||||
@@ -21,7 +21,7 @@ class StreamQualitySettingsSectionHook: ClassHook<NSObject> {
|
||||
|
||||
//
|
||||
|
||||
func showOfflineModePopUp() {
|
||||
private func showOfflineModePopUp() {
|
||||
PopUpHelper.showPopUp(
|
||||
message: "Native playlist downloading is server-sided and is not available with this tweak. You can download podcast episodes though.",
|
||||
buttonText: "OK"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import SwiftUI
|
||||
|
||||
extension EeveeSettingsView {
|
||||
|
||||
@ViewBuilder func LyricsColorsSection() -> some View {
|
||||
|
||||
Section(
|
||||
header: Text("Lyrics Background Color"),
|
||||
footer: Text("""
|
||||
If you turn on Display Original Colors, the lyrics will appear in the original Spotify colors for tracks that have them.
|
||||
|
||||
You can set a static color or a normalization factor based on the extracted track cover's color. This factor determines how much dark colors are lightened and light colors are darkened. Generally, you will see lighter colors with a higher normalization factor.
|
||||
""")) {
|
||||
Toggle(
|
||||
"Display Original Colors",
|
||||
isOn: $lyricsColors.displayOriginalColors
|
||||
)
|
||||
|
||||
Toggle(
|
||||
"Use Static Color",
|
||||
isOn: $lyricsColors.useStaticColor
|
||||
)
|
||||
|
||||
if lyricsColors.useStaticColor {
|
||||
ColorPicker(
|
||||
"Static Color",
|
||||
selection: Binding<Color>(
|
||||
get: { Color(hex: lyricsColors.staticColor) },
|
||||
set: { lyricsColors.staticColor = $0.hexString }
|
||||
),
|
||||
|
||||
supportsOpacity: false
|
||||
)
|
||||
}
|
||||
else {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
|
||||
Text("Color Normalization Factor")
|
||||
|
||||
Slider(
|
||||
value: $lyricsColors.normalizationFactor,
|
||||
in: 0.2...0.8,
|
||||
step: 0.1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: lyricsColors) { newLyricsColors in
|
||||
UserDefaults.lyricsColors = newLyricsColors
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,30 @@ If you have an active Premium subscription, you can turn on Do Not Patch Premium
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: patchType) { newPatchType in
|
||||
|
||||
UserDefaults.patchType = newPatchType
|
||||
|
||||
do {
|
||||
try OfflineHelper.resetOfflineBnk()
|
||||
}
|
||||
catch {
|
||||
NSLog("Unable to reset offline.bnk: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: overwriteConfiguration) { overwriteConfiguration in
|
||||
|
||||
UserDefaults.overwriteConfiguration = overwriteConfiguration
|
||||
|
||||
do {
|
||||
try OfflineHelper.resetOfflineBnk()
|
||||
}
|
||||
catch {
|
||||
NSLog("Unable to reset offline.bnk: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if patchType == .requests {
|
||||
Section(
|
||||
footer: Text("Replace remote configuration with the dumped Premium one. It might fix some issues, such as appearing ads, but it's not guaranteed.")
|
||||
|
||||
@@ -7,50 +7,7 @@ struct EeveeSettingsView: View {
|
||||
@State var patchType = UserDefaults.patchType
|
||||
@State var lyricsSource = UserDefaults.lyricsSource
|
||||
@State var overwriteConfiguration = UserDefaults.overwriteConfiguration
|
||||
|
||||
private func getMusixmatchToken(_ input: String) -> String? {
|
||||
|
||||
if let match = input.firstMatch("\\[UserToken\\]: ([a-f0-9]+)"),
|
||||
let tokenRange = Range(match.range(at: 1), in: input) {
|
||||
return String(input[tokenRange])
|
||||
}
|
||||
else if input ~= "^[a-f0-9]+$" {
|
||||
return input
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func showMusixmatchTokenAlert(_ oldSource: LyricsSource) {
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Enter User Token",
|
||||
message: "In order to use Musixmatch, you need to retrieve your user token from the official app. Download Musixmatch from the App Store, sign up, then go to Settings > Get help > Copy debug info, and paste it here. You can also extract the token using MITM.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
alert.addTextField() { textField in
|
||||
textField.placeholder = "---- Debug Info ---- [Device]: iPhone"
|
||||
}
|
||||
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
||||
lyricsSource = oldSource
|
||||
})
|
||||
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
|
||||
let text = alert.textFields!.first!.text!
|
||||
|
||||
guard let token = getMusixmatchToken(text) else {
|
||||
lyricsSource = oldSource
|
||||
return
|
||||
}
|
||||
|
||||
musixmatchToken = token
|
||||
UserDefaults.lyricsSource = .musixmatch
|
||||
})
|
||||
|
||||
WindowHelper.shared.present(alert)
|
||||
}
|
||||
@State var lyricsColors = UserDefaults.lyricsColors
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -59,6 +16,8 @@ struct EeveeSettingsView: View {
|
||||
PremiumSections()
|
||||
|
||||
LyricsSections()
|
||||
|
||||
LyricsColorsSection()
|
||||
|
||||
Section {
|
||||
Toggle(
|
||||
@@ -87,54 +46,8 @@ struct EeveeSettingsView: View {
|
||||
|
||||
.animation(.default, value: lyricsSource)
|
||||
.animation(.default, value: patchType)
|
||||
|
||||
.onChange(of: musixmatchToken) { input in
|
||||
|
||||
if input.isEmpty { return }
|
||||
|
||||
if let token = getMusixmatchToken(input) {
|
||||
UserDefaults.musixmatchToken = token
|
||||
self.musixmatchToken = token
|
||||
}
|
||||
else {
|
||||
self.musixmatchToken = ""
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: lyricsSource) { [lyricsSource] newSource in
|
||||
|
||||
if newSource == .musixmatch && musixmatchToken.isEmpty {
|
||||
showMusixmatchTokenAlert(lyricsSource)
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.lyricsSource = newSource
|
||||
}
|
||||
.animation(.default, value: lyricsColors)
|
||||
|
||||
.onChange(of: patchType) { newPatchType in
|
||||
|
||||
UserDefaults.patchType = newPatchType
|
||||
|
||||
do {
|
||||
try OfflineHelper.resetOfflineBnk()
|
||||
}
|
||||
catch {
|
||||
NSLog("Unable to reset offline.bnk: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: overwriteConfiguration) { overwriteConfiguration in
|
||||
|
||||
UserDefaults.overwriteConfiguration = overwriteConfiguration
|
||||
|
||||
do {
|
||||
try OfflineHelper.resetOfflineBnk()
|
||||
}
|
||||
catch {
|
||||
NSLog("Unable to reset offline.bnk: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
.onAppear {
|
||||
UIView.appearance(
|
||||
whenContainedInInstancesOf: [UIAlertController.self]
|
||||
|
||||
@@ -2,6 +2,51 @@ import SwiftUI
|
||||
|
||||
extension EeveeSettingsView {
|
||||
|
||||
private func getMusixmatchToken(_ input: String) -> String? {
|
||||
|
||||
if let match = input.firstMatch("\\[UserToken\\]: ([a-f0-9]+)"),
|
||||
let tokenRange = Range(match.range(at: 1), in: input) {
|
||||
return String(input[tokenRange])
|
||||
}
|
||||
else if input ~= "^[a-f0-9]+$" {
|
||||
return input
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func showMusixmatchTokenAlert(_ oldSource: LyricsSource) {
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Enter User Token",
|
||||
message: "In order to use Musixmatch, you need to retrieve your user token from the official app. Download Musixmatch from the App Store, sign up, then go to Settings > Get help > Copy debug info, and paste it here. You can also extract the token using MITM.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
alert.addTextField() { textField in
|
||||
textField.placeholder = "---- Debug Info ---- [Device]: iPhone"
|
||||
}
|
||||
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
||||
lyricsSource = oldSource
|
||||
})
|
||||
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
|
||||
let text = alert.textFields!.first!.text!
|
||||
|
||||
guard let token = getMusixmatchToken(text) else {
|
||||
lyricsSource = oldSource
|
||||
return
|
||||
}
|
||||
|
||||
musixmatchToken = token
|
||||
UserDefaults.lyricsSource = .musixmatch
|
||||
})
|
||||
|
||||
WindowHelper.shared.present(alert)
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder func LyricsSections() -> some View {
|
||||
|
||||
Section(footer: Text("""
|
||||
@@ -36,6 +81,29 @@ If the tweak is unable to find a song or process the lyrics, you'll see a "Could
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: musixmatchToken) { input in
|
||||
|
||||
if input.isEmpty { return }
|
||||
|
||||
if let token = getMusixmatchToken(input) {
|
||||
UserDefaults.musixmatchToken = token
|
||||
self.musixmatchToken = token
|
||||
}
|
||||
else {
|
||||
self.musixmatchToken = ""
|
||||
}
|
||||
}
|
||||
|
||||
.onChange(of: lyricsSource) { [lyricsSource] newSource in
|
||||
|
||||
if newSource == .musixmatch && musixmatchToken.isEmpty {
|
||||
showMusixmatchTokenAlert(lyricsSource)
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.lyricsSource = newSource
|
||||
}
|
||||
|
||||
if lyricsSource != .genius {
|
||||
Section(
|
||||
|
||||
Reference in New Issue
Block a user