diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics+AllTracksLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics+AllTracksLyrics.x.swift index 6e954ef..c1428c3 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics+AllTracksLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics+AllTracksLyrics.x.swift @@ -20,7 +20,7 @@ class SPTPlayerTrackHook: ClassHook { guard shouldOverrideLocalTrackURI, let absoluteString = uri?.absoluteString, - absoluteString.hasPrefix("spotify:local:") else { + absoluteString.isLocalTrackIdentifier else { return uri } diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift index 3c47348..a6b10bf 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift @@ -128,7 +128,9 @@ func getLyricsDataForCurrentTrack(_ originalPath: String, originalLyrics: Lyrics throw LyricsError.noCurrentTrack } - if !originalPath.contains(track.trackIdentifier) { + let trackIdentifier = track.trackIdentifier + + if !trackIdentifier.isEmpty && !originalPath.contains(trackIdentifier) { throw LyricsError.trackMismatch } diff --git a/Sources/EeveeSpotify/Lyrics/Models/Extensions/String+IsLocalTrackIdentifierExtension.swift b/Sources/EeveeSpotify/Lyrics/Models/Extensions/String+IsLocalTrackIdentifierExtension.swift new file mode 100644 index 0000000..1b93c8f --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Extensions/String+IsLocalTrackIdentifierExtension.swift @@ -0,0 +1,5 @@ +extension String { + var isLocalTrackIdentifier: Bool { + self.hasPrefix("spotify:local:") + } +} diff --git a/Sources/EeveeSpotify/Lyrics/NowPlayingScrollViewControllerInstanceHook.x.swift b/Sources/EeveeSpotify/Lyrics/NowPlayingScrollViewControllerInstanceHook.x.swift index 6de1e7c..bd72429 100644 --- a/Sources/EeveeSpotify/Lyrics/NowPlayingScrollViewControllerInstanceHook.x.swift +++ b/Sources/EeveeSpotify/Lyrics/NowPlayingScrollViewControllerInstanceHook.x.swift @@ -8,6 +8,26 @@ var scrollDataSource: NowPlayingScrollDataSourceImplementation? var nowPlayingScrollViewController: NowPlayingScrollViewController? var npvScrollViewController: NPVScrollViewController? +class LegacyNowPlayingPlatformSwiftServiceImplementationHook: ClassHook { + typealias Group = IOS14PremiumPatchingGroup + static let targetName = "NowPlaying_PlatformImpl.NowPlayingPlatformSwiftServiceImplementation" + + func provideStatefulPlayer() -> StatefulPlayerImplementation { + statefulPlayer = orig.provideStatefulPlayer() + return statefulPlayer! + } +} + +class NowPlayingPlatformSwiftServiceImplementationHook: ClassHook { + typealias Group = NonIOS14PremiumPatchingGroup + static let targetName = "NowPlaying_PlatformImpl.NowPlayingPlatformSwiftServiceImplementation" + + func provideStatefulPlayerWithFeatureIdentifier(_ identifier: NSString) -> StatefulPlayerImplementation { + statefulPlayer = orig.provideStatefulPlayerWithFeatureIdentifier(identifier) + return statefulPlayer! + } +} + class NowPlayingScrollPrivateServiceImplementationHook: ClassHook { typealias Group = BaseLyricsGroup static let targetName = "NowPlaying_ScrollImpl.NowPlayingScrollPrivateServiceImplementation" @@ -22,7 +42,6 @@ class NowPlayingScrollPrivateServiceImplementationHook: ClassHook { ) } else { - statefulPlayer = Ivars(dependencies).statefulPlayer scrollDataSource = Ivars(target) .$__lazy_storage_$_scrollDataSource npvScrollViewController = Dynamic.convert( diff --git a/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyingFunctions.swift b/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyingFunctions.swift index 5957194..474ebd3 100644 --- a/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyingFunctions.swift +++ b/Sources/EeveeSpotify/Premium/DynamicPremium+ModifyingFunctions.swift @@ -2,30 +2,65 @@ import Foundation import UIKit func modifyRemoteConfiguration(_ configuration: inout UcsResponse) { + modifyAttributes(&configuration.attributes.accountAttributes) + if UserDefaults.overwriteConfiguration { configuration.resolve.configuration = try! BundleHelper.shared.resolveConfiguration() } - - modifyAttributes(&configuration.attributes.accountAttributes) - modifyAssignedValues(&configuration.assignedValues) + else { + modifyAssignedValues(&configuration.assignedValues) + } } -private let propertyToRemoveNames = [ - "enable_common_capping", - "enable_pns_common_capping", - "enable_pick_and_shuffle_common_capping", - "enable_pick_and_shuffle_dynamic_cap", - "pick_and_shuffle_timecap", // capping - "should_nova_scroll_use_scrollsita" // 😡😡😡 - // spotify, stop changing the scroll logic +private let propertyReplacements = [ + // capping + EeveePropertyReplacement(name: "enable_common_capping", modification: .remove), + EeveePropertyReplacement(name: "enable_pns_common_capping", modification: .remove), + EeveePropertyReplacement(name: "enable_pick_and_shuffle_common_capping", modification: .remove), + EeveePropertyReplacement(name: "enable_pick_and_shuffle_dynamic_cap", modification: .remove), + EeveePropertyReplacement(name: "pick_and_shuffle_timecap", modification: .remove), + EeveePropertyReplacement(scope: "ios-feature-queue", modification: .remove), + + // also capping idk + EeveePropertyReplacement(name: "enable_free_on_demand_experiment", modification: .remove), + EeveePropertyReplacement(name: "enable_free_on_demand_context_menu_experiment", modification: .remove), + EeveePropertyReplacement(name: "enable_mft_plus_queue", modification: .remove), + EeveePropertyReplacement(name: "enable_mft_plus_extended_queue", modification: .remove), + EeveePropertyReplacement(name: "enable_playback_timeout_service", modification: .setBool(false)), + EeveePropertyReplacement(name: "enable_playback_timeout_error_ui", modification: .setBool(false)), + EeveePropertyReplacement(name: "playback_timeout_action", modification: .setEnum("Nothing")), + EeveePropertyReplacement(name: "is_remove_from_queue_enabled_for_mft_plus", modification: .remove), + EeveePropertyReplacement(name: "is_reordering_for_mft_plus_allowed", modification: .remove), + + // 😡😡😡 spotify, stop changing the scroll logic + EeveePropertyReplacement(name: "should_nova_scroll_use_scrollsita", modification: .remove) ] -func modifyAssignedValues(_ values: inout [AssignedValue]) { - values.removeAll(where: { propertyToRemoveNames.contains($0.propertyID.name) }) - values.removeAll(where: { $0.propertyID.scope == "ios-feature-queue" }) +private func modifyAssignedValues(_ values: inout [AssignedValue]) { + for replacement in propertyReplacements { + let matchingIndices = values.indices.filter({ index in + let value = values[index] + let nameMatches = replacement.name.map { value.propertyID.name == $0 } ?? true + let scopeMatches = replacement.scope.map { value.propertyID.scope == $0 } ?? true + return nameMatches && scopeMatches + }) + + for index in matchingIndices.sorted(by: >) { + switch replacement.modification { + case .remove: + values.remove(at: index) + + case .setBool(let newValue): + values[index].boolValue = BoolValue.with { $0.value = newValue } + + case .setEnum(let newValue): + values[index].enumValue = EnumValue.with { $0.value = newValue } + } + } + } } -func modifyAttributes(_ attributes: inout [String: AccountAttribute]) { +private func modifyAttributes(_ attributes: inout [String: AccountAttribute]) { let oneYearFromNow = Calendar.current.date(byAdding: .year, value: 1, to: Date())! let formatter = ISO8601DateFormatter() diff --git a/Sources/EeveeSpotify/Premium/Models/PatchType.swift b/Sources/EeveeSpotify/Premium/Models/EeveePatchType.swift similarity index 82% rename from Sources/EeveeSpotify/Premium/Models/PatchType.swift rename to Sources/EeveeSpotify/Premium/Models/EeveePatchType.swift index 9916520..44a9eb7 100644 --- a/Sources/EeveeSpotify/Premium/Models/PatchType.swift +++ b/Sources/EeveeSpotify/Premium/Models/EeveePatchType.swift @@ -1,6 +1,6 @@ import Foundation -enum PatchType: Int { +enum EeveePatchType: Int { case notSet case disabled case requests diff --git a/Sources/EeveeSpotify/Premium/Models/EeveePropertyReplacement.swift b/Sources/EeveeSpotify/Premium/Models/EeveePropertyReplacement.swift new file mode 100644 index 0000000..87191c7 --- /dev/null +++ b/Sources/EeveeSpotify/Premium/Models/EeveePropertyReplacement.swift @@ -0,0 +1,17 @@ +enum EeveePropertyModification { + case remove + case setBool(Bool) + case setEnum(String) +} + +struct EeveePropertyReplacement { + let scope: String? + let name: String? + let modification: EeveePropertyModification + + init(name: String? = nil, scope: String? = nil, modification: EeveePropertyModification) { + self.name = name + self.scope = scope + self.modification = modification + } +} diff --git a/Sources/EeveeSpotify/Premium/Models/Account.pb.swift b/Sources/EeveeSpotify/Premium/Models/Protobuf/Account.pb.swift similarity index 100% rename from Sources/EeveeSpotify/Premium/Models/Account.pb.swift rename to Sources/EeveeSpotify/Premium/Models/Protobuf/Account.pb.swift diff --git a/Sources/EeveeSpotify/Premium/Models/PremiumPlanRow.pb.swift b/Sources/EeveeSpotify/Premium/Models/Protobuf/PremiumPlanRow.pb.swift similarity index 100% rename from Sources/EeveeSpotify/Premium/Models/PremiumPlanRow.pb.swift rename to Sources/EeveeSpotify/Premium/Models/Protobuf/PremiumPlanRow.pb.swift diff --git a/Sources/EeveeSpotify/Premium/Models/SpotifyPlan.pb.swift b/Sources/EeveeSpotify/Premium/Models/Protobuf/SpotifyPlan.pb.swift similarity index 100% rename from Sources/EeveeSpotify/Premium/Models/SpotifyPlan.pb.swift rename to Sources/EeveeSpotify/Premium/Models/Protobuf/SpotifyPlan.pb.swift diff --git a/Sources/EeveeSpotify/Premium/Models/YourPremiumBadge.pb.swift b/Sources/EeveeSpotify/Premium/Models/Protobuf/YourPremiumBadge.pb.swift similarity index 100% rename from Sources/EeveeSpotify/Premium/Models/YourPremiumBadge.pb.swift rename to Sources/EeveeSpotify/Premium/Models/Protobuf/YourPremiumBadge.pb.swift diff --git a/Sources/EeveeSpotify/Premium/TrackRowsEnabler.x.swift b/Sources/EeveeSpotify/Premium/TrackRowsEnabler.x.swift index aac8ad1..b95c173 100644 --- a/Sources/EeveeSpotify/Premium/TrackRowsEnabler.x.swift +++ b/Sources/EeveeSpotify/Premium/TrackRowsEnabler.x.swift @@ -1,7 +1,7 @@ import Orion class SPTFreeTierArtistHubRemoteURLResolverHook: ClassHook { - typealias Group = BasePremiumPatchingGroup + typealias Group = IOS14And15PremiumPatchingGroup static let targetName = "SPTFreeTierArtistHubRemoteURLResolver" func initWithViewURI( diff --git a/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift b/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift index 4f51bfa..cc27c90 100644 --- a/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift +++ b/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift @@ -29,10 +29,10 @@ extension UserDefaults { } } - static var patchType: PatchType { + static var patchType: EeveePatchType { get { if let rawValue = container.object(forKey: patchTypeKey) as? Int { - return PatchType(rawValue: rawValue) ?? .requests + return EeveePatchType(rawValue: rawValue) ?? .requests } return .notSet