musixmatch anonymous tokens

This commit is contained in:
eevee
2024-11-07 00:49:54 +03:00
parent d579470f2c
commit 21b3267ac3
14 changed files with 120 additions and 54 deletions
@@ -126,7 +126,7 @@ class LyricsOnlyViewControllerHook: ClassHook<UIViewController> {
private func loadLyricsForCurrentTrack() throws {
guard let track = HookedInstances.currentTrack else {
throw LyricsError.NoCurrentTrack
throw LyricsError.noCurrentTrack
}
//
@@ -145,7 +145,7 @@ private func loadLyricsForCurrentTrack() throws {
case .lrclib: LrcLibLyricsRepository()
case .musixmatch: MusixmatchLyricsRepository.shared
case .petit: PetitLyricsRepository()
case .notReplaced: throw LyricsError.InvalidSource
case .notReplaced: throw LyricsError.invalidSource
}
let lyricsDto: LyricsDto
@@ -163,7 +163,7 @@ private func loadLyricsForCurrentTrack() throws {
switch error {
case .InvalidMusixmatchToken:
case .invalidMusixmatchToken:
if !hasShownUnauthorizedPopUp {
PopUpHelper.showPopUp(
delayed: false,
@@ -174,7 +174,7 @@ private func loadLyricsForCurrentTrack() throws {
hasShownUnauthorizedPopUp.toggle()
}
case .MusixmatchRestricted:
case .musixmatchRestricted:
if !hasShownRestrictedPopUp {
PopUpHelper.showPopUp(
delayed: false,
@@ -190,7 +190,7 @@ private func loadLyricsForCurrentTrack() throws {
}
}
else {
lastLyricsState.fallbackError = .UnknownError
lastLyricsState.fallbackError = .unknownError
}
if source == .genius || !UserDefaults.geniusFallback {
@@ -221,7 +221,7 @@ private func loadLyricsForCurrentTrack() throws {
func getLyricsForCurrentTrack(originalLyrics: Lyrics? = nil) throws -> Data {
guard let track = HookedInstances.currentTrack else {
throw LyricsError.NoCurrentTrack
throw LyricsError.noCurrentTrack
}
var lyrics = preloadedLyrics
@@ -232,7 +232,7 @@ func getLyricsForCurrentTrack(originalLyrics: Lyrics? = nil) throws -> Data {
}
guard var lyrics = lyrics else {
throw LyricsError.UnknownError
throw LyricsError.unknownError
}
let lyricsColorsSettings = UserDefaults.lyricsColors
@@ -1,23 +1,23 @@
import Foundation
enum LyricsError: Error, CustomStringConvertible {
case NoCurrentTrack
case MusixmatchRestricted
case InvalidMusixmatchToken
case DecodingError
case NoSuchSong
case UnknownError
case InvalidSource
case noCurrentTrack
case musixmatchRestricted
case invalidMusixmatchToken
case decodingError
case noSuchSong
case unknownError
case invalidSource
var description: String {
switch self {
case .NoSuchSong: "no_such_song".localized
case .MusixmatchRestricted: "musixmatch_restricted".localized
case .InvalidMusixmatchToken: "invalid_musixmatch_token".localized
case .DecodingError: "decoding_error".localized
case .NoCurrentTrack: "no_current_track".localized
case .UnknownError: "unknown_error".localized
case .InvalidSource: ""
case .noSuchSong: "no_such_song".localized
case .musixmatchRestricted: "musixmatch_restricted".localized
case .invalidMusixmatchToken: "invalid_musixmatch_token".localized
case .decodingError: "decoding_error".localized
case .noCurrentTrack: "no_current_track".localized
case .unknownError: "unknown_error".localized
case .invalidSource: ""
}
}
}
@@ -53,7 +53,7 @@ struct GeniusLyricsRepository: LyricsRepository {
}
guard let rootResponse = try? jsonDecoder.decode(GeniusRootResponse.self, from: data!) else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
return rootResponse.response
}
@@ -67,7 +67,7 @@ struct GeniusLyricsRepository: LyricsRepository {
case .sections(let sectionsResponse) = data,
let section = sectionsResponse.sections.first
else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
return section.hits
@@ -77,7 +77,7 @@ struct GeniusLyricsRepository: LyricsRepository {
let data = try perform("/songs/\(songId)", query: ["text_format": "plain"])
guard case .song(let songResponse) = data else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
return songResponse.song
@@ -131,7 +131,7 @@ struct GeniusLyricsRepository: LyricsRepository {
let hits = try searchSong("\(strippedTitle) \(query.primaryArtist)")
guard !hits.isEmpty else {
throw LyricsError.NoSuchSong
throw LyricsError.noSuchSong
}
var hasFoundRomanizedLyrics = false
@@ -97,7 +97,7 @@ struct LrcLibLyricsRepository: LyricsRepository {
let songs = try searchSong("\(strippedTitle) \(query.primaryArtist)")
guard let song = mostRelevantSong(songs: songs, strippedTitle: strippedTitle) else {
throw LyricsError.NoSuchSong
throw LyricsError.noSuchSong
}
if let syncedLyrics = song.syncedLyrics {
@@ -110,7 +110,7 @@ struct LrcLibLyricsRepository: LyricsRepository {
}
guard let plainLyrics = song.plainLyrics else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
let lines = Array(plainLyrics.components(separatedBy: "\n").dropLast())
@@ -22,9 +22,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
var finalQuery = query
finalQuery["usertoken"] = UserDefaults.musixmatchToken
finalQuery["app_id"] = UIDevice.current.isIpad
? "mac-ios-ipad-v1.0"
: "mac-ios-v2.0"
finalQuery["app_id"] = UIDevice.current.musixmatchAppId
let queryString = finalQuery.queryString.addingPercentEncoding(
withAllowedCharacters: .urlHostAllowed
@@ -63,12 +61,12 @@ class MusixmatchLyricsRepository: LyricsRepository {
let body = message["body"] as? [String: Any],
let macroCalls = body["macro_calls"] as? [String: Any]
else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
if let header = message["header"] as? [String: Any],
header["status_code"] as? Int == 401 {
throw LyricsError.InvalidMusixmatchToken
throw LyricsError.invalidMusixmatchToken
}
return macroCalls
@@ -81,11 +79,11 @@ class MusixmatchLyricsRepository: LyricsRepository {
let firstSubtitle = subtitleList.first,
let subtitle = firstSubtitle["subtitle"] as? [String: Any]
else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
if let restricted = subtitle["restricted"] as? Bool, restricted {
throw LyricsError.MusixmatchRestricted
throw LyricsError.musixmatchRestricted
}
return subtitle
@@ -108,7 +106,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
let body = message["body"] as? [String: Any],
let translationsList = body["translations_list"] as? [[String: Any]]
else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
let translations = translationsList.compactMap {
@@ -236,7 +234,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
let lyricsStatusCode = lyricsHeader["status_code"] as? Int {
if lyricsStatusCode == 404 {
throw LyricsError.NoSuchSong
throw LyricsError.noSuchSong
}
if let lyricsBody = lyricsMessage["body"] as? [String: Any],
@@ -245,7 +243,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
let plainLyrics = lyrics["lyrics_body"] as? String {
if let restricted = lyrics["restricted"] as? Bool, restricted {
throw LyricsError.MusixmatchRestricted
throw LyricsError.musixmatchRestricted
}
return LyricsDto(
@@ -259,6 +257,6 @@ class MusixmatchLyricsRepository: LyricsRepository {
}
}
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
}
@@ -44,7 +44,7 @@ struct PetitLyricsRepository: LyricsRepository {
}
guard let response = try? XMLDecoder().decode(PetitResponse.self, from: data!) else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
return response
@@ -58,7 +58,7 @@ struct PetitLyricsRepository: LyricsRepository {
)
guard let song = response.songs.first else {
throw LyricsError.NoSuchSong
throw LyricsError.noSuchSong
}
return song
@@ -81,7 +81,7 @@ struct PetitLyricsRepository: LyricsRepository {
)
guard let song = response.songs.first else {
throw LyricsError.NoSuchSong
throw LyricsError.noSuchSong
}
return song
@@ -97,7 +97,7 @@ struct PetitLyricsRepository: LyricsRepository {
)
guard let lyricsData = Data(base64Encoded: song.lyricsData) else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
switch song.lyricsType {
@@ -105,7 +105,7 @@ struct PetitLyricsRepository: LyricsRepository {
case .wordsSynced:
guard let lyrics = try? XMLDecoder().decode(PetitLyricsData.self, from: lyricsData)
else {
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
return LyricsDto(
@@ -132,7 +132,7 @@ struct PetitLyricsRepository: LyricsRepository {
)
default:
throw LyricsError.DecodingError
throw LyricsError.decodingError
}
}
}
@@ -4,4 +4,10 @@ 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,22 @@
import Foundation
import UIKit
struct AnonymousTokenHelper {
private static let apiUrl = "https://apic.musixmatch.com"
static func requestAnonymousMusixmatchToken() async throws -> String {
let url = URL(string: "\(apiUrl)/ws/1.1/token.get?app_id=\(await UIDevice.current.musixmatchAppId)")!
let (data, _) = try await URLSession.shared.data(from: url)
guard
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let message = json["message"] as? [String: Any],
let body = message["body"] as? [String: Any],
let userToken = body["user_token"] as? String
else {
throw AnonymousTokenError.invalidResponse
}
return userToken
}
}
@@ -0,0 +1,3 @@
enum AnonymousTokenError: Swift.Error {
case invalidResponse
}
@@ -13,10 +13,17 @@ extension EeveeLyricsSettingsView {
return nil
}
func showMusixmatchTokenAlert(_ oldSource: LyricsSource) {
func showMusixmatchTokenAlert(_ oldSource: LyricsSource, showAnonymousTokenOption: Bool) {
var message = "enter_user_token_message".localized
if showAnonymousTokenOption {
message.append("\n\n")
message.append("request_anonymous_token_description".localized)
}
let alert = UIAlertController(
title: "enter_user_token".localized,
message: "enter_user_token_message".localized,
message: message,
preferredStyle: .alert
)
@@ -27,6 +34,25 @@ extension EeveeLyricsSettingsView {
alert.addAction(UIAlertAction(title: "Cancel".uiKitLocalized, style: .cancel) { _ in
lyricsSource = oldSource
})
if showAnonymousTokenOption {
alert.addAction(UIAlertAction(title: "request_anonymous_token".localized, style: .default) { _ in
Task {
defer {
isRequestingMusixmatchToken.toggle()
}
do {
isRequestingMusixmatchToken.toggle()
musixmatchToken = try await AnonymousTokenHelper.requestAnonymousMusixmatchToken()
UserDefaults.lyricsSource = .musixmatch
}
catch {
showMusixmatchTokenAlert(oldSource, showAnonymousTokenOption: false)
}
}
})
}
alert.addAction(UIAlertAction(title: "OK".uiKitLocalized, style: .default) { _ in
let text = alert.textFields!.first!.text!
@@ -25,7 +25,7 @@ extension EeveeLyricsSettingsView {
}
.onChange(of: lyricsSource) { [lyricsSource] newSource in
if newSource == .musixmatch && musixmatchToken.isEmpty {
showMusixmatchTokenAlert(lyricsSource)
showMusixmatchTokenAlert(lyricsSource, showAnonymousTokenOption: true)
return
}
@@ -4,9 +4,11 @@ struct EeveeLyricsSettingsView: View {
@State var musixmatchToken = UserDefaults.musixmatchToken
@State var lyricsSource = UserDefaults.lyricsSource
@State var geniusFallback = UserDefaults.geniusFallback
@State var lyricsOptions = UserDefaults.lyricsOptions
@State var showLanguageWarning = false
@State private var lyricsOptions = UserDefaults.lyricsOptions
@State var isRequestingMusixmatchToken = false
@State private var isShowingLanguageWarning = false
var body: some View {
List {
@@ -46,7 +48,7 @@ struct EeveeLyricsSettingsView: View {
if lyricsSource == .musixmatch {
Section {
HStack {
if showLanguageWarning {
if isShowingLanguageWarning {
Image(systemName: "exclamationmark.triangle")
.font(.title3)
.foregroundColor(.yellow)
@@ -75,8 +77,11 @@ struct EeveeLyricsSettingsView: View {
}
.listStyle(GroupedListStyle())
.disabled(isRequestingMusixmatchToken)
.animation(.default, value: lyricsSource)
.animation(.default, value: showLanguageWarning)
.animation(.default, value: isRequestingMusixmatchToken)
.animation(.default, value: isShowingLanguageWarning)
.animation(.default, value: geniusFallback)
.onChange(of: geniusFallback) { geniusFallback in
@@ -87,7 +92,7 @@ struct EeveeLyricsSettingsView: View {
let selectedLanguage = lyricsOptions.musixmatchLanguage
if selectedLanguage.isEmpty || selectedLanguage ~= "^[\\w\\d]{2}$" {
showLanguageWarning = false
isShowingLanguageWarning = false
MusixmatchLyricsRepository.shared.selectedLanguage = selectedLanguage
UserDefaults.lyricsOptions = lyricsOptions
@@ -95,7 +100,7 @@ struct EeveeLyricsSettingsView: View {
return
}
showLanguageWarning = true
isShowingLanguageWarning = true
}
}
}
@@ -110,3 +110,6 @@ let_the_music_play = "Let the music play...";
liked_songs = "Liked songs";
contributors = "Contributors";
request_anonymous_token = "Request Anonymous Token";
request_anonymous_token_description = "Tap “Request Anonymous Token,” so EeveeSpotify will try to request the token from Musixmatch without authorization.";
@@ -108,3 +108,6 @@ let_the_music_play = "Пусть заиграет музыка...";
liked_songs = "Тебе понравилось";
contributors = "Участники";
request_anonymous_token = "Запросить анонимный токен";
request_anonymous_token_description = "Нажмите Запросить анонимный токен, и EeveeSpotify попытается запросить токен у Musixmatch без авторизации.";