mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
musixmatch anonymous tokens
This commit is contained in:
@@ -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
|
||||
}
|
||||
+28
-2
@@ -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!
|
||||
|
||||
+1
-1
@@ -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 без авторизации.";
|
||||
|
||||
Reference in New Issue
Block a user