diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-03-15 22:10:06 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2024-03-15 22:10:06 +0100 |
| commit | 44e7b4de4073e6dc25681bb2fa6977bf5869689a (patch) | |
| tree | 164c8d8d980732e64d96fe6e788373108144d085 /Sources | |
| parent | b2096fb3958d9e9cdcca77931aadfb947cab24ee (diff) | |
Add macos sources
Diffstat (limited to 'Sources')
| -rw-r--r-- | Sources/lyricli/configuration.swift | 70 | ||||
| -rw-r--r-- | Sources/lyricli/errors/configuration_could_not_be_read.swift | 3 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_disabled.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_enabled.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_could_not_be_reset.swift | 6 | ||||
| -rw-r--r-- | Sources/lyricli/errors/source_not_available.swift | 3 | ||||
| -rw-r--r-- | Sources/lyricli/lyricli.swift | 102 | ||||
| -rw-r--r-- | Sources/lyricli/lyricli_command.swift | 95 | ||||
| -rw-r--r-- | Sources/lyricli/lyrics_engine.swift | 142 | ||||
| -rw-r--r-- | Sources/lyricli/source_manager.swift | 51 | ||||
| -rw-r--r-- | Sources/lyricli/sources/apple_music_source.swift | 80 | ||||
| -rw-r--r-- | Sources/lyricli/sources/source_protocol.swift | 9 | ||||
| -rw-r--r-- | Sources/lyricli/track.swift | 15 |
13 files changed, 0 insertions, 588 deletions
diff --git a/Sources/lyricli/configuration.swift b/Sources/lyricli/configuration.swift deleted file mode 100644 index b2defc1..0000000 --- a/Sources/lyricli/configuration.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -// Reads and writes the configuration. Config keys are accessed as a dictionary. -class Configuration { - // Location of the global configuration file - private let configurationPath = NSString(string: "~/.lyricli.conf").expandingTildeInPath - - // Default options, will be automatically written to the global config if - // not found. - private var configuration: [String: Any] = [ - "enabled_sources": ["apple_music", "spotify"] - ] - - // The shared instance of the object - static let shared: Configuration = Configuration() - - private init() { - - // Read the config file and attempt to set any of the values. Otherwise - // don't do anything. - - if let data = try? NSData(contentsOfFile: configurationPath) as Data { - if let parsedConfig = try? JSONSerialization.jsonObject(with: data) { - if let parsedConfig = parsedConfig as? [String: Any] { - for (key, value) in parsedConfig { - - if key == "enabled_sources" { - if let value = value as? [String] { - configuration[key] = value - } - } else { - if let value = value as? String { - configuration[key] = value - } - } - } - } - } - } - - writeConfiguration() - } - - // Write the configuration back to the file - private func writeConfiguration() { - - var error: NSError? - - if let outputStream = OutputStream(toFileAtPath: configurationPath, append: false) { - outputStream.open() - JSONSerialization.writeJSONObject(configuration, - to: outputStream, - options: [JSONSerialization.WritingOptions.prettyPrinted], - error: &error) - outputStream.close() - } - } - - // Allow access to the config properties as a dictionary - subscript(index: String) -> Any? { - get { - return configuration[index] - } - - set(newValue) { - configuration[index] = newValue - writeConfiguration() - } - } -} diff --git a/Sources/lyricli/errors/configuration_could_not_be_read.swift b/Sources/lyricli/errors/configuration_could_not_be_read.swift deleted file mode 100644 index fb2b61a..0000000 --- a/Sources/lyricli/errors/configuration_could_not_be_read.swift +++ /dev/null @@ -1,3 +0,0 @@ -struct ConfigurationCouldNotBeRead: Error { - var localizedDescription = "The configuration could not be read, check ~/.lyricli.conf" -} diff --git a/Sources/lyricli/errors/source_could_not_be_disabled.swift b/Sources/lyricli/errors/source_could_not_be_disabled.swift deleted file mode 100644 index b19f3c5..0000000 --- a/Sources/lyricli/errors/source_could_not_be_disabled.swift +++ /dev/null @@ -1,6 +0,0 @@ -// Future improvement: At the moment the sources don't need any special -// configuration. Once we do have more operations it would make sense -// to throw more descriptive errors. -struct SourceCouldNotBeDisabled: Error { - var localizedDescription = "The selected source failed while disabling" -} diff --git a/Sources/lyricli/errors/source_could_not_be_enabled.swift b/Sources/lyricli/errors/source_could_not_be_enabled.swift deleted file mode 100644 index 7797478..0000000 --- a/Sources/lyricli/errors/source_could_not_be_enabled.swift +++ /dev/null @@ -1,6 +0,0 @@ -// Future improvement: At the moment the sources don't need any special -// configuration. Once we do have more operations it would make sense -// to throw more descriptive errors. -struct SourceCouldNotBeEnabled: Error { - var localizedDescription = "The selected source failed while enabling" -} diff --git a/Sources/lyricli/errors/source_could_not_be_reset.swift b/Sources/lyricli/errors/source_could_not_be_reset.swift deleted file mode 100644 index beecf54..0000000 --- a/Sources/lyricli/errors/source_could_not_be_reset.swift +++ /dev/null @@ -1,6 +0,0 @@ -// Future improvement: At the moment the sources don't need any special -// configuration. Once we do have more operations it would make sense -// to throw more descriptive errors. -struct SourceCouldNotBeReset: Error { - var localizedDescription = "The selected source failed while resetting" -} diff --git a/Sources/lyricli/errors/source_not_available.swift b/Sources/lyricli/errors/source_not_available.swift deleted file mode 100644 index e3d598d..0000000 --- a/Sources/lyricli/errors/source_not_available.swift +++ /dev/null @@ -1,3 +0,0 @@ -struct SourceNotAvailable: Error { - var localizedDescription = "The selected source wasn't available" -} diff --git a/Sources/lyricli/lyricli.swift b/Sources/lyricli/lyricli.swift deleted file mode 100644 index d5e5b91..0000000 --- a/Sources/lyricli/lyricli.swift +++ /dev/null @@ -1,102 +0,0 @@ -// The main class, handles all the actions that the executable will call -class Lyricli { - - // Version of the application - static var version = "2.0.1" - - // Flag that controls whether we should show the track artist and name before - // the lyrics - static var showTitle = false - - // Obtains the name of the current track from a source, fetches the lyrics - // from an engine and prints them - static func printLyrics() { - - let sourceManager = SourceManager() - - if let currentTrack = sourceManager.currentTrack { - printLyrics(currentTrack) - } else { - print("No Artist/Song could be found :(") - } - } - - // fetches the lyrics from an engine and prints them - static func printLyrics(_ currentTrack: Track) { - let engine = LyricsEngine(withTrack: currentTrack) - - if showTitle { - printTitle(currentTrack) - } - if let lyrics = engine.lyrics { - print(lyrics) - } else { - print("Lyrics not found :(") - } - } - - // Print the currently available sources - static func printSources() { - let sourceManager = SourceManager() - for (sourceName, _) in sourceManager.availableSources { - if (Configuration.shared["enabled_sources"] as? [String] ?? []).contains(sourceName) { - print("\(sourceName) (enabled)") - } else { - print(sourceName) - } - } - } - - // Runs the enable method of a source and writes the configuration to set it - // as enabled - static func enableSource(_ sourceName: String) throws { - let sourceManager = SourceManager() - if let source = sourceManager.availableSources[sourceName] { - if let enabledSources = Configuration.shared["enabled_sources"] as? [String] { - if source.enable() == false { - throw SourceCouldNotBeEnabled() - } - if !enabledSources.contains(sourceName) { - Configuration.shared["enabled_sources"] = enabledSources + [sourceName] - } - return - } - throw ConfigurationCouldNotBeRead() - } - throw SourceNotAvailable() - } - - // Remove a source from the enabled sources configuration - static func disableSource(_ sourceName: String) throws { - let sourceManager = SourceManager() - if let source = sourceManager.availableSources[sourceName] { - if let enabledSources = Configuration.shared["enabled_sources"] as? [String] { - if source.disable() == false { - throw SourceCouldNotBeDisabled() - } - Configuration.shared["enabled_sources"] = enabledSources.filter { $0 != sourceName } - return - } - throw ConfigurationCouldNotBeRead() - } - throw SourceNotAvailable() - } - - // Removes any configuration for a source, and disables it - static func resetSource(_ sourceName: String) throws { - let sourceManager = SourceManager() - if let source = sourceManager.availableSources[sourceName] { - if source.reset() == false { - throw SourceCouldNotBeReset() - } - try disableSource(sourceName) - return - } - throw SourceNotAvailable() - } - - // Prints the track artist and name - private static func printTitle(_ track: Track) { - print("\(track.artist) - \(track.name)") - } -} diff --git a/Sources/lyricli/lyricli_command.swift b/Sources/lyricli/lyricli_command.swift deleted file mode 100644 index 872ab6e..0000000 --- a/Sources/lyricli/lyricli_command.swift +++ /dev/null @@ -1,95 +0,0 @@ -import Darwin -import ArgumentParser - -@main -struct LyricliCommand: ParsableCommand { - - // Positional Arguments - @Argument var artist: String? - @Argument var trackName: String? - - // Flags - @Flag(name: .shortAndLong, help: "Prints the version.") - var version = false - - @Flag(name: [.long, .customShort("t")], help: "Shows title of track if true") - var showTitle = false - - @Flag(name: .shortAndLong, help: "Lists all sources") - var listSources = false - - // Named Arguments - @Option(name: .shortAndLong, help: ArgumentHelp("Enables a source", valueName: "source")) - var enableSource: String? - - @Option(name: .shortAndLong, help: ArgumentHelp("Disables a source", valueName: "source")) - var disableSource: String? - - @Option(name: .shortAndLong, help: ArgumentHelp("Resets a source", valueName: "source")) - var resetSource: String? - - mutating func run() throws { - - // Handle the version flag - if version { - print(Lyricli.version) - Darwin.exit(0) - } - - // Handle the list sources flag - if listSources { - Lyricli.printSources() - Darwin.exit(0) - } - - // Handle the enable source option - if let source = enableSource { - do { - try Lyricli.enableSource(source) - } catch let error { - handleErrorAndQuit(error) - } - Darwin.exit(0) - } - - // Handle the disable source option - if let source = disableSource { - do { - try Lyricli.disableSource(source) - } catch let error { - handleErrorAndQuit(error) - } - Darwin.exit(0) - } - - // Handle the reset source flag - if let source = resetSource { - do { - try Lyricli.resetSource(source) - } catch let error { - handleErrorAndQuit(error) - } - Darwin.exit(0) - } - - Lyricli.showTitle = showTitle - - if let artist { - let currentTrack: Track - if let trackName { - currentTrack = Track(withName: trackName, andArtist: artist) - } else { - currentTrack = Track(withName: "", andArtist: artist) - } - Lyricli.printLyrics(currentTrack) - Darwin.exit(0) - } - - Lyricli.printLyrics() - } - - private func handleErrorAndQuit(_ error: Error) { - fputs(error.localizedDescription, stderr) - Darwin.exit(1) - } -} diff --git a/Sources/lyricli/lyrics_engine.swift b/Sources/lyricli/lyrics_engine.swift deleted file mode 100644 index e0752d7..0000000 --- a/Sources/lyricli/lyrics_engine.swift +++ /dev/null @@ -1,142 +0,0 @@ -import Foundation -import SwiftSoup - -// Given a track, attempts to fetch the lyrics from lyricswiki -class LyricsEngine { - - private let clientToken = <GENIUS_CLIENT_TOKEN> - - // URL of the API endpoint to use - private let apiURL = "https://api.genius.com/search" - - // Method used to call the API - private let apiMethod = "GET" - - // The track we'll be looking for - private let track: Track - - // Fetches the lyrics and returns if found - var lyrics: String? { - - var lyrics: String? - - // Encode the track artist and name and finish building the API call URL - - if let artist = track.artist.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - if let name = track.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - - if let url = URL(string: "\(apiURL)?access_token=\(clientToken)&q=\(artist)%20\(name)") { - - // We'll lock until the async call is finished - - var requestFinished = false - let asyncLock = NSCondition() - asyncLock.lock() - - // Call the API and unlock when you're done - - searchLyricsUsingAPI(withURL: url, completionHandler: {lyricsResult -> Void in - lyrics = lyricsResult - requestFinished = true - asyncLock.signal() - }) - - while !requestFinished { - asyncLock.wait() - } - asyncLock.unlock() - } - } - } - - return lyrics - } - - // Initializes with a track - init(withTrack targetTrack: Track) { - - track = targetTrack - } - - // Fetch the lyrics URL from the API, triggers the request to fetch the - // lyrics page - private func searchLyricsUsingAPI(withURL url: URL, completionHandler: @escaping (String?) -> Void) { - - var apiRequest = URLRequest(url: url) - apiRequest.httpMethod = "GET" - - let task = URLSession.shared.dataTask(with: apiRequest, completionHandler: {data, _, _ -> Void in - - // If the response is parseable JSON, and has a url, we'll look for - // the lyrics in there - - if let data = data { - if let jsonResponse = try? JSONSerialization.jsonObject(with: data) { - if let jsonResponse = jsonResponse as? [String: Any] { - if let response = jsonResponse["response"] as? [String: Any] { - if let hits = response["hits"] as? [[String: Any]] { - let filteredHits = hits.filter { $0["type"] as? String == "song" } - if filteredHits.count > 0 { - let firstHit = hits[0] - if let firstHitData = firstHit["result"] as? [String: Any] { - if let lyricsUrlString = firstHitData["url"] as? String { - if let lyricsUrl = URL(string: lyricsUrlString) { - - // At this point we have a valid wiki url - self.fetchLyricsFromPage( - withURL: lyricsUrl, - completionHandler: completionHandler - ) - return - } - } - } - } - } - } - } - } - } - - completionHandler(nil) - }) - task.resume() - } - - // Fetch the lyrics from the page and send it to the parser - private func fetchLyricsFromPage(withURL url: URL, completionHandler: @escaping (String?) -> Void) { - - var pageRequest = URLRequest(url: url) - pageRequest.httpMethod = "GET" - - let task = URLSession.shared.dataTask(with: pageRequest, completionHandler: {data, _, _ -> Void in - - // If the response is parseable JSON, and has a url, we'll look for - // the lyrics in there - - if let data = data { - if let htmlBody = String(data: data, encoding: String.Encoding.utf8) { - self.parseHtmlBody(htmlBody, completionHandler: completionHandler) - return - } - } - - completionHandler(nil) - }) - task.resume() - } - - // Parses the wiki to find the lyrics, decodes the lyrics object - private func parseHtmlBody(_ body: String, completionHandler: @escaping (String?) -> Void) { - - do { - let document: Document = try SwiftSoup.parse(body) - let lyricsBox = try document.select("div[data-lyrics-container=\"true\"]") - try lyricsBox.select("br").after("\\n") - let lyrics = try lyricsBox.text() - completionHandler(lyrics.replacingOccurrences(of: "\\n", with: "\r\n")) - } catch { - completionHandler(nil) - } - } -} diff --git a/Sources/lyricli/source_manager.swift b/Sources/lyricli/source_manager.swift deleted file mode 100644 index 481d327..0000000 --- a/Sources/lyricli/source_manager.swift +++ /dev/null @@ -1,51 +0,0 @@ -// Collect and manage the available and enabled source -class SourceManager { - - // List of sources enabled for the crurent platform - var availableSources: [String: Source] = [ - "apple_music": AppleMusicSource(), - "spotify": SpotifySource() - ] - - // Iterate over the sources until we find a track or run out of sources - var currentTrack: Track? { - for source in enabledSources { - if let currentTrack = source.currentTrack { - return currentTrack - } - } - - return nil - } - - // Return the list of enabled sources based on the configuration - var enabledSources: [Source] { - - // Checks the config and returns an array of sources based on the - // enabled and available ones - - var sources = [Source]() - - if let sourceNames = Configuration.shared["enabled_sources"] as? [String] { - for sourceName in sourceNames { - if let source = availableSources[sourceName] { - sources.append(source) - } - } - } - - return sources - } - - // Given a source name, it will enable it and add it to the enabled sources config - func enable(sourceName: String) { - } - - // Given a source name, it will remove it from the enabled sources config - func disable(sourceName: String) { - } - - // Given a source name, it removes any stored configuration and disables it - func reset(sourceName: String) { - } -} diff --git a/Sources/lyricli/sources/apple_music_source.swift b/Sources/lyricli/sources/apple_music_source.swift deleted file mode 100644 index 5eee588..0000000 --- a/Sources/lyricli/sources/apple_music_source.swift +++ /dev/null @@ -1,80 +0,0 @@ -import ScriptingBridge -import Foundation - -// Protocol to obtain the track from -@objc protocol AppleMusicTrack { - @objc optional var name: String {get} - @objc optional var artist: String {get} -} - -// Protocol to interact with Apple Music -@objc protocol AppleMusicApplication { - @objc optional var currentTrack: AppleMusicTrack? {get} - @objc optional var currentStreamTitle: String? {get} -} - -extension SBApplication: AppleMusicApplication {} - -// Source that reads track artist and name from current itunes track -class AppleMusicSource: Source { - - // Calls the spotify API and returns the current track - var currentTrack: Track? { - - if let appleMusic: AppleMusicApplication = SBApplication(bundleIdentifier: bundleIdentifier) { - if let application = appleMusic as? SBApplication { - if !application.isRunning { - return nil - } - } - - // Attempt to fetch the title from a stream - if let currentStreamTitle = appleMusic.currentStreamTitle { - if let track = currentStreamTitle { - - let trackComponents = track.split(separator: "-").map(String.init) - - if trackComponents.count == 2 { - let artist = trackComponents[0].trimmingCharacters(in: .whitespaces) - let name = trackComponents[1].trimmingCharacters(in: .whitespaces) - - return Track(withName: name, andArtist: artist) - } - - } - } - - // Attempt to fetch the title from a song - if let currentTrack = appleMusic.currentTrack { - if let track = currentTrack { - if let name = track.name { - if let artist = track.artist { - - // track properties are empty strings if itunes is closed - if name == "" || artist == "" { - return nil - } - return Track(withName: name, andArtist: artist) - } - } - } - } - } - - return nil - } - - private var bundleIdentifier: String { - if ProcessInfo().isOperatingSystemAtLeast( - OperatingSystemVersion(majorVersion: 10, minorVersion: 15, patchVersion: 0) - ) { - return "com.apple.Music" - } - - return "com.apple.iTunes" - } - - func enable() -> Bool { return true } - func disable() -> Bool { return true } - func reset() -> Bool { return true } -} diff --git a/Sources/lyricli/sources/source_protocol.swift b/Sources/lyricli/sources/source_protocol.swift deleted file mode 100644 index 8d52603..0000000 --- a/Sources/lyricli/sources/source_protocol.swift +++ /dev/null @@ -1,9 +0,0 @@ -// All sources should comply with this protocol. The currentTrack computed -// property will return a track if the conditions are met -protocol Source { - var currentTrack: Track? { get } - - func disable() -> Bool - func enable() -> Bool - func reset() -> Bool -} diff --git a/Sources/lyricli/track.swift b/Sources/lyricli/track.swift deleted file mode 100644 index ead4359..0000000 --- a/Sources/lyricli/track.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Holds the name and artist of a track -class Track { - - // The name of the track to search for - let name: String - - // The name of the artist - let artist: String - - init(withName trackName: String, andArtist trackArtist: String) { - - name = trackName - artist = trackArtist - } -} |