diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift index b3531b73..498c51ad 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift @@ -47,23 +47,36 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { ) } - catch { - - if source != .genius && UserDefaults.geniusFallback { + catch let error as LyricsError { + + switch error { + + case .InvalidMusixmatchToken: - 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(), - spotifyTrackId: track.URI().spt_trackIdentifier(), - source: source + PopUpHelper.showPopUp( + delayed: false, + message: "The tweak is unable to load lyrics from Musixmatch due to Unauthorized error. Please check or update your Musixmatch token.", + buttonText: "OK" ) + break + + default: + break } - else { + + 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(), + spotifyTrackId: track.URI().spt_trackIdentifier(), + source: source + ) } let lyrics = try Lyrics.with { diff --git a/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift b/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift index b134b300..c20c78a3 100644 --- a/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift +++ b/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift @@ -65,6 +65,11 @@ struct MusixmatchLyricsDataSource { else { throw LyricsError.DecodingError } + + if let header = message["header"] as? [String: Any], + header["status_code"] as? Int == 401 { + throw LyricsError.InvalidMusixmatchToken + } if let trackSubtitlesGet = macroCalls["track.subtitles.get"] as? [String: Any], let subtitlesMessage = trackSubtitlesGet["message"] as? [String: Any], diff --git a/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift b/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift index ef1f6ce2..d8a8401a 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift @@ -2,6 +2,7 @@ import Foundation enum LyricsError: Swift.Error { case NoCurrentTrack + case InvalidMusixmatchToken case DecodingError case NoSuchSong -} \ No newline at end of file +} diff --git a/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift b/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift index 55e47f34..88572b98 100644 --- a/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift +++ b/Sources/EeveeSpotify/Models/Extensions/String+Extension.swift @@ -44,4 +44,19 @@ extension String { withTemplate: "" ) } + + 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 + } } diff --git a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift index 6d9630b7..04b17c58 100644 --- a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift +++ b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift @@ -9,6 +9,7 @@ extension UserDefaults { private static let geniusFallbackKey = "geniusFallback" private static let darkPopUpsKey = "darkPopUps" private static let patchTypeKey = "patchType" + private static let overwriteConfigurationKey = "overwriteConfiguration" static var lyricsSource: LyricsSource { get { @@ -62,4 +63,13 @@ extension UserDefaults { defaults.set(patchType.rawValue, forKey: patchTypeKey) } } + + static var overwriteConfiguration: Bool { + get { + defaults.bool(forKey: overwriteConfigurationKey) + } + set (overwriteConfiguration) { + defaults.set(overwriteConfiguration, forKey: overwriteConfigurationKey) + } + } } diff --git a/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyFunctions.swift b/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyFunctions.swift index 785bf0d2..d86e1a30 100644 --- a/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyFunctions.swift +++ b/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyFunctions.swift @@ -1,6 +1,11 @@ import Foundation func modifyRemoteConfiguration(_ configuration: inout UcsResponse) { + + if UserDefaults.overwriteConfiguration { + configuration.resolve.configuration = try! BundleHelper.shared.resolveConfiguration() + } + modifyAttributes(&configuration.attributes.accountAttributes) } diff --git a/Sources/EeveeSpotify/Premium/DynamicPremium.x.swift b/Sources/EeveeSpotify/Premium/DynamicPremium.x.swift index 4314381e..ffea8c49 100644 --- a/Sources/EeveeSpotify/Premium/DynamicPremium.x.swift +++ b/Sources/EeveeSpotify/Premium/DynamicPremium.x.swift @@ -62,6 +62,19 @@ class SPTCoreURLSessionDataDelegateHook: ClassHook { var bootstrapMessage = try BootstrapMessage(serializedData: buffer) + if UserDefaults.patchType == .notSet { + + if bootstrapMessage.attributes["type"]?.stringValue == "premium" { + UserDefaults.patchType = .disabled + showHavePremiumPopUp() + } + else { + UserDefaults.patchType = .requests + } + + NSLog("[EeveeSpotify] Fetched bootstrap, \(UserDefaults.patchType) was set") + } + if UserDefaults.patchType == .requests { modifyRemoteConfiguration(&bootstrapMessage.ucsResponse) @@ -75,21 +88,6 @@ class SPTCoreURLSessionDataDelegateHook: ClassHook { NSLog("[EeveeSpotify] Modified bootstrap data") } else { - - if UserDefaults.patchType == .notSet { - - if bootstrapMessage.attributes["type"]?.stringValue == "premium" { - UserDefaults.patchType = .disabled - showHavePremiumPopUp() - } - else { - UserDefaults.patchType = .offlineBnk - showOfflineBnkMethodSetPopUp() - } - - NSLog("[EeveeSpotify] Fetched bootstrap, \(UserDefaults.patchType) was set") - } - orig.URLSession(session, dataTask: task, didReceiveData: buffer) } diff --git a/Sources/EeveeSpotify/Premium/Helpers/BundleHelper.swift b/Sources/EeveeSpotify/Premium/Helpers/BundleHelper.swift index 2e285313..1d43e60a 100755 --- a/Sources/EeveeSpotify/Premium/Helpers/BundleHelper.swift +++ b/Sources/EeveeSpotify/Premium/Helpers/BundleHelper.swift @@ -26,4 +26,15 @@ class BundleHelper { )! ) } + + func resolveConfiguration() throws -> ResolveConfiguration { + return try ResolveConfiguration( + serializedData: try Data( + contentsOf: self.bundle.url( + forResource: "resolveconfiguration", + withExtension: "bnk" + )! + ) + ) + } } diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSection.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSections.swift similarity index 72% rename from Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSection.swift rename to Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSections.swift index ffdd3a19..f298ce0c 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSection.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView+PremiumSections.swift @@ -2,7 +2,7 @@ import SwiftUI extension EeveeSettingsView { - @ViewBuilder func PremiumSection() -> some View { + @ViewBuilder func PremiumSections() -> some View { Section(footer: patchType == .disabled ? nil : Text(""" You can select the Premium patching method you prefer. App restart is required after changing. @@ -17,7 +17,7 @@ If you have an active Premium subscription, you can turn on Do Not Patch Premium "Do Not Patch Premium", isOn: Binding( get: { patchType == .disabled }, - set: { patchType = $0 ? .disabled : .offlineBnk } + set: { patchType = $0 ? .disabled : .requests } ) ) @@ -31,5 +31,16 @@ If you have an active Premium subscription, you can turn on Do Not Patch Premium } } } + + 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.") + ) { + Toggle( + "Overwrite Configuration", + isOn: $overwriteConfiguration + ) + } + } } } diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift index b74d71ad..e04028b6 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift @@ -6,6 +6,20 @@ struct EeveeSettingsView: View { @State var musixmatchToken = UserDefaults.musixmatchToken @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) { @@ -25,16 +39,8 @@ struct EeveeSettingsView: View { alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in let text = alert.textFields!.first!.text! - let token: String - - if let match = text.firstMatch("\\[UserToken\\]: ([a-f0-9]+)"), - let tokenRange = Range(match.range(at: 1), in: text) { - token = String(text[tokenRange]) - } - else if text ~= "^[a-f0-9]+$" { - token = text - } - else { + + guard let token = getMusixmatchToken(text) else { lyricsSource = oldSource return } @@ -50,7 +56,7 @@ struct EeveeSettingsView: View { List { - PremiumSection() + PremiumSections() LyricsSections() @@ -73,14 +79,26 @@ struct EeveeSettingsView: View { } } } - - .padding(.bottom, 45) + + .listStyle(GroupedListStyle()) + + .padding(.bottom, 60) + .ignoresSafeArea(.keyboard) .animation(.default, value: lyricsSource) .animation(.default, value: patchType) - .onChange(of: musixmatchToken) { token in - UserDefaults.musixmatchToken = token + .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 @@ -104,8 +122,18 @@ struct EeveeSettingsView: View { NSLog("Unable to reset offline.bnk: \(error)") } } - - .listStyle(GroupedListStyle()) + + .onChange(of: overwriteConfiguration) { overwriteConfiguration in + + UserDefaults.overwriteConfiguration = overwriteConfiguration + + do { + try OfflineHelper.resetOfflineBnk() + } + catch { + NSLog("Unable to reset offline.bnk: \(error)") + } + } .onAppear { UIView.appearance( diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift index 4678b202..5070322b 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift @@ -30,7 +30,7 @@ If the tweak is unable to find a song or process the lyrics, you'll see a "Could Text("Musixmatch User Token") - TextField("Enter User Token", text: $musixmatchToken) + TextField("Enter User Token or Paste Debug Info", text: $musixmatchToken) .foregroundColor(.gray) } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/layout/Library/Application Support/EeveeSpotify.bundle/resolveconfiguration.bnk b/layout/Library/Application Support/EeveeSpotify.bundle/resolveconfiguration.bnk new file mode 100644 index 00000000..3bf289ee Binary files /dev/null and b/layout/Library/Application Support/EeveeSpotify.bundle/resolveconfiguration.bnk differ