From fdafe0d4012af00e0d9cb613a0146924b8fd8eaf Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Sun, 14 Apr 2019 16:08:54 +0200 Subject: Update swift files to use Bariloche --- Sources/arguments_source.swift | 18 --- Sources/configuration.swift | 70 ------------ Sources/itunes_source.swift | 61 ----------- Sources/lyricli.swift | 60 ---------- Sources/lyricli/configuration.swift | 70 ++++++++++++ Sources/lyricli/lyricli.swift | 64 +++++++++++ Sources/lyricli/lyricli_command.swift | 38 +++++++ Sources/lyricli/lyrics_engine.swift | 146 +++++++++++++++++++++++++ Sources/lyricli/main.swift | 107 ++++++++++++++++++ Sources/lyricli/source_manager.swift | 51 +++++++++ Sources/lyricli/sources/itunes_source.swift | 61 +++++++++++ Sources/lyricli/sources/source_protocol.swift | 5 + Sources/lyricli/sources/spotify_source.swift | 41 +++++++ Sources/lyricli/track.swift | 15 +++ Sources/lyrics_engine.swift | 146 ------------------------- Sources/main.swift | 151 -------------------------- Sources/source_manager.swift | 52 --------- Sources/source_protocol.swift | 5 - Sources/spotify_source.swift | 41 ------- Sources/track.swift | 15 --- 20 files changed, 598 insertions(+), 619 deletions(-) delete mode 100644 Sources/arguments_source.swift delete mode 100644 Sources/configuration.swift delete mode 100644 Sources/itunes_source.swift delete mode 100644 Sources/lyricli.swift create mode 100644 Sources/lyricli/configuration.swift create mode 100644 Sources/lyricli/lyricli.swift create mode 100644 Sources/lyricli/lyricli_command.swift create mode 100644 Sources/lyricli/lyrics_engine.swift create mode 100644 Sources/lyricli/main.swift create mode 100644 Sources/lyricli/source_manager.swift create mode 100644 Sources/lyricli/sources/itunes_source.swift create mode 100644 Sources/lyricli/sources/source_protocol.swift create mode 100644 Sources/lyricli/sources/spotify_source.swift create mode 100644 Sources/lyricli/track.swift delete mode 100644 Sources/lyrics_engine.swift delete mode 100644 Sources/main.swift delete mode 100644 Sources/source_manager.swift delete mode 100644 Sources/source_protocol.swift delete mode 100644 Sources/spotify_source.swift delete mode 100644 Sources/track.swift (limited to 'Sources') diff --git a/Sources/arguments_source.swift b/Sources/arguments_source.swift deleted file mode 100644 index 9615318..0000000 --- a/Sources/arguments_source.swift +++ /dev/null @@ -1,18 +0,0 @@ -// Source that reads track artist and name from the command line -class ArgumentsSource: Source { - - // Returns a track based on the arguments. It assumes the track artist - // will be the first argument, and the name will be the second, excluding - // any flags. - var currentTrack: Track? { - - if CommandLine.arguments.count >= 3 { - // expected usage: $ ./lyricli - let trackName: String = CommandLine.arguments[2] - let trackArtist: String = CommandLine.arguments[1] - - return Track(withName: trackName, andArtist: trackArtist) - } - return nil - } -} diff --git a/Sources/configuration.swift b/Sources/configuration.swift deleted file mode 100644 index 05e2802..0000000 --- a/Sources/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": ["arguments", "itunes", "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/itunes_source.swift b/Sources/itunes_source.swift deleted file mode 100644 index 6f4aef9..0000000 --- a/Sources/itunes_source.swift +++ /dev/null @@ -1,61 +0,0 @@ -import ScriptingBridge - -// Protocol to obtain the track from iTunes -@objc protocol iTunesTrack { - @objc optional var name: String {get} - @objc optional var artist: String {get} -} - -// Protocol to interact with iTunes -@objc protocol iTunesApplication { - @objc optional var currentTrack: iTunesTrack? {get} - @objc optional var currentStreamTitle: String? {get} -} - -extension SBApplication : iTunesApplication {} - -// Source that reads track artist and name from current itunes track -class ItunesSource: Source { - - // Calls the spotify API and returns the current track - var currentTrack: Track? { - - if let iTunes: iTunesApplication = SBApplication(bundleIdentifier: "com.apple.iTunes") { - - // Attempt to fetch the title from a stream - if let currentStreamTitle = iTunes.currentStreamTitle { - if let track = currentStreamTitle { - - let trackComponents = track.characters.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 = iTunes.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 - } - -} diff --git a/Sources/lyricli.swift b/Sources/lyricli.swift deleted file mode 100644 index 8aeb5c0..0000000 --- a/Sources/lyricli.swift +++ /dev/null @@ -1,60 +0,0 @@ -// The main class, handles all the actions that the executable will call -class Lyricli { - - // Version of the application - static var version = "0.3.0" - - // 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 { - let engine = LyricsEngine(withTrack: currentTrack) - - if let lyrics = engine.lyrics { - if showTitle { - printTitle(currentTrack) - } - - print(lyrics) - } else { - print("Lyrics not found :(") - } - - } else { - print("No Artist/Song could be found :(") - } - } - - // Print the currently available sources - static func printSources() { - print("Listing Sources: Not yet implemented") - } - - // Runs the enable method of a source and writes the configuration to set it - // as enabled - static func enableSource(_ sourceName: String) { - print("Enable source \(sourceName): Not yet implemented") - } - - // Remove a source from the enabled sources configuration - static func disableSource(_ sourceName: String) { - print("Disable source \(sourceName): Not yet implemented") - } - - // Removes any configuration for a source, and disables it - static func resetSource(_ sourceName: String) { - print("Reset source \(sourceName): Not yet implemented") - } - - // Prints the track artist and name - private static func printTitle(_ track: Track) { - print("\(track.artist) - \(track.name)") - } -} diff --git a/Sources/lyricli/configuration.swift b/Sources/lyricli/configuration.swift new file mode 100644 index 0000000..1b01034 --- /dev/null +++ b/Sources/lyricli/configuration.swift @@ -0,0 +1,70 @@ +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": ["itunes", "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/lyricli.swift b/Sources/lyricli/lyricli.swift new file mode 100644 index 0000000..c4136fb --- /dev/null +++ b/Sources/lyricli/lyricli.swift @@ -0,0 +1,64 @@ +// The main class, handles all the actions that the executable will call +class Lyricli { + + // Version of the application + static var version = "0.4.0" + + // 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 let lyrics = engine.lyrics { + if showTitle { + printTitle(currentTrack) + } + + print(lyrics) + } else { + print("Lyrics not found :(") + } + } + + // Print the currently available sources + static func printSources() { + print("Listing Sources: Not yet implemented") + } + + // Runs the enable method of a source and writes the configuration to set it + // as enabled + static func enableSource(_ sourceName: String) { + print("Enable source \(sourceName): Not yet implemented") + } + + // Remove a source from the enabled sources configuration + static func disableSource(_ sourceName: String) { + print("Disable source \(sourceName): Not yet implemented") + } + + // Removes any configuration for a source, and disables it + static func resetSource(_ sourceName: String) { + print("Reset source \(sourceName): Not yet implemented") + } + + // 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 new file mode 100644 index 0000000..28ad44a --- /dev/null +++ b/Sources/lyricli/lyricli_command.swift @@ -0,0 +1,38 @@ +import Bariloche + +class LyricliCommand: Command { + let usage: String? = "Fetch the lyrics for current playing track or the one specified via arguments" + + // Flags + let version = Flag(short: "v", long: "version", help: "Prints the version.") + let showTitle = Flag(short: "t", long: "title", help: "Shows title of song if true") + let listSources = Flag(short: "l", long: "listSources", help: "Lists all sources") + + // Named Arguments + let enableSource = Argument(name: "source", + kind: .named(short: "e", long: "enableSource"), + optional: true, + help: "Enables a source") + let disableSource = Argument(name: "source", + kind: .named(short: "d", long: "disableSource"), + optional: true, + help: "Disables a source") + let resetSource = Argument(name: "source", + kind: .named(short: "r", long: "resetSource"), + optional: true, + help: "Resets a source") + + // Positional Arguments + let artist = Argument(name: "artist", + kind: .positional, + optional: true, + help: "The name of the artist") + let trackName = Argument(name: "trackName", + kind: .positional, + optional: true, + help: "The name of the track") + + func run() -> Bool { + return true + } +} diff --git a/Sources/lyricli/lyrics_engine.swift b/Sources/lyricli/lyrics_engine.swift new file mode 100644 index 0000000..085a61c --- /dev/null +++ b/Sources/lyricli/lyrics_engine.swift @@ -0,0 +1,146 @@ +import Foundation +import HTMLEntities + +// Given a track, attempts to fetch the lyrics from lyricswiki +class LyricsEngine { + + // URL of the API endpoint to use + private let apiURL = "https://lyrics.wikia.com/api.php?action=lyrics&func=getSong&fmt=realjson" + + // Method used to call the API + private let apiMethod = "GET" + + // Regular expxression used to find the lyrics in the lyricswiki HTML + private let lyricsMatcher = "class='lyricbox'>(.+) 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 fetchLyricsFromAPI(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 lyricsUrlString = jsonResponse["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) { + + // Look for the lyrics lightbox + + if let regex = try? NSRegularExpression(pattern: lyricsMatcher) { + let matches = regex.matches(in: body, range: NSRange(location: 0, length: body.count)) + + for match in matches { + + let nsBody = body as NSString + let range = match.range(at: 1) + let encodedLyrics = nsBody.substring(with: range) + + let decodedLyrics = decodeLyrics(encodedLyrics) + + completionHandler(decodedLyrics) + return + } + } + + completionHandler(nil) + } + + // Escapes the HTML entities + private func decodeLyrics(_ lyrics: String) -> String { + + let unescapedLyrics = lyrics.htmlUnescape() + return unescapedLyrics.replacingOccurrences(of: "
", with: "\n") + } +} diff --git a/Sources/lyricli/main.swift b/Sources/lyricli/main.swift new file mode 100644 index 0000000..be1c933 --- /dev/null +++ b/Sources/lyricli/main.swift @@ -0,0 +1,107 @@ +import Foundation +import Bariloche + +// Entry point of the application. This is the main executable +private func main() { + + // Bariloche assumes at least one argument, so bypass + // if that's the case. + if CommandLine.arguments.count > 1 { + let parser = Bariloche(command: LyricliCommand()) + let result = parser.parse() + + if result.count == 0 { + exit(EX_USAGE) + } + + if let lyricliCommand = result[0] as? LyricliCommand { + // Flags + checkVersionFlag(lyricliCommand) + checkListSourcesFlag(lyricliCommand) + checkTitleFlag(lyricliCommand) + + // String Options + + checkEnableSourceFlag(lyricliCommand) + checkDisableSourceFlag(lyricliCommand) + checkResetSourceFlag(lyricliCommand) + + checkPositionalArguments(lyricliCommand) + + } + } + + // Run Lyricli + Lyricli.printLyrics() +} + +// Handle the version flag + +private func checkVersionFlag(_ command: LyricliCommand) { + if command.version.value { + print(Lyricli.version) + exit(0) + } +} + +// Handle the list sources flag + +private func checkListSourcesFlag(_ command: LyricliCommand) { + if command.listSources.value { + Lyricli.printSources() + exit(0) + } +} + +// Handle the title flag + +private func checkTitleFlag(_ command: LyricliCommand) { + Lyricli.showTitle = command.showTitle.value +} + +// Handle the enable source flag + +private func checkEnableSourceFlag(_ command: LyricliCommand) { + if let source = command.enableSource.value { + Lyricli.enableSource(source) + exit(0) + } +} + +// Handle the disable source flag + +private func checkDisableSourceFlag(_ command: LyricliCommand) { + if let source = command.disableSource.value { + Lyricli.disableSource(source) + exit(0) + } +} + +// Handle the reset source flag + +private func checkResetSourceFlag(_ command: LyricliCommand) { + if let source = command.resetSource.value { + Lyricli.resetSource(source) + exit(0) + } +} + +// Handle the positional arguments + +private func checkPositionalArguments(_ command: LyricliCommand) { + if let artist = command.artist.value { + + let currentTrack: Track + + if let trackName = command.trackName.value { + currentTrack = Track(withName: trackName, andArtist: artist) + } else { + currentTrack = Track(withName: "", andArtist: artist) + } + + Lyricli.printLyrics(currentTrack) + exit(0) + } +} + +main() diff --git a/Sources/lyricli/source_manager.swift b/Sources/lyricli/source_manager.swift new file mode 100644 index 0000000..2f0b8f4 --- /dev/null +++ b/Sources/lyricli/source_manager.swift @@ -0,0 +1,51 @@ +// Collect and manage the available and enabled source +class SourceManager { + + // List of sources enabled for the crurent platform + private var availableSources: [String: Source] = [ + "itunes": ItunesSource(), + "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/itunes_source.swift b/Sources/lyricli/sources/itunes_source.swift new file mode 100644 index 0000000..4e175c1 --- /dev/null +++ b/Sources/lyricli/sources/itunes_source.swift @@ -0,0 +1,61 @@ +import ScriptingBridge + +// Protocol to obtain the track from iTunes +@objc protocol iTunesTrack { + @objc optional var name: String {get} + @objc optional var artist: String {get} +} + +// Protocol to interact with iTunes +@objc protocol iTunesApplication { + @objc optional var currentTrack: iTunesTrack? {get} + @objc optional var currentStreamTitle: String? {get} +} + +extension SBApplication: iTunesApplication {} + +// Source that reads track artist and name from current itunes track +class ItunesSource: Source { + + // Calls the spotify API and returns the current track + var currentTrack: Track? { + + if let iTunes: iTunesApplication = SBApplication(bundleIdentifier: "com.apple.iTunes") { + + // Attempt to fetch the title from a stream + if let currentStreamTitle = iTunes.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 = iTunes.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 + } + +} diff --git a/Sources/lyricli/sources/source_protocol.swift b/Sources/lyricli/sources/source_protocol.swift new file mode 100644 index 0000000..0885994 --- /dev/null +++ b/Sources/lyricli/sources/source_protocol.swift @@ -0,0 +1,5 @@ +// 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 } +} diff --git a/Sources/lyricli/sources/spotify_source.swift b/Sources/lyricli/sources/spotify_source.swift new file mode 100644 index 0000000..2e56c8e --- /dev/null +++ b/Sources/lyricli/sources/spotify_source.swift @@ -0,0 +1,41 @@ +import ScriptingBridge + +// Protocol to obtain the track from Spotify +@objc protocol SpotifyTrack { + @objc optional var name: String {get} + @objc optional var artist: String {get} +} + +// Protocol to interact with Spotify +@objc protocol SpotifyApplication { + @objc optional var currentTrack: SpotifyTrack? {get} +} + +extension SBApplication : SpotifyApplication {} + +// Source that reads track artist and name from current Spotify track +class SpotifySource: Source { + + // Calls the spotify API and returns the current track + var currentTrack: Track? { + + if let spotify: SpotifyApplication = SBApplication(bundleIdentifier: "com.spotify.client") { + + // Attempt to fetch the title from a song + if let currentTrack = spotify.currentTrack { + if let track = currentTrack { + if let name = track.name { + if let artist = track.artist { + + return Track(withName: name, andArtist: artist) + } + } + } + } + } + + return nil + } + +} + diff --git a/Sources/lyricli/track.swift b/Sources/lyricli/track.swift new file mode 100644 index 0000000..ead4359 --- /dev/null +++ b/Sources/lyricli/track.swift @@ -0,0 +1,15 @@ +// 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 + } +} diff --git a/Sources/lyrics_engine.swift b/Sources/lyrics_engine.swift deleted file mode 100644 index 27e0e11..0000000 --- a/Sources/lyrics_engine.swift +++ /dev/null @@ -1,146 +0,0 @@ -import Foundation -import HTMLEntities - -// Given a track, attempts to fetch the lyrics from lyricswiki -class LyricsEngine { - - // URL of the API endpoint to use - private let apiURL = "https://lyrics.wikia.com/api.php?action=lyrics&func=getSong&fmt=realjson" - - // Method used to call the API - private let apiMethod = "GET" - - // Regular expxression used to find the lyrics in the lyricswiki HTML - private let lyricsMatcher = "class='lyricbox'>(.+) 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 fetchLyricsFromAPI(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 lyricsUrlString = jsonResponse["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) { - - // Look for the lyrics lightbox - - if let regex = try? NSRegularExpression(pattern: lyricsMatcher) { - let matches = regex.matches(in: body, range: NSRange(location: 0, length: body.characters.count)) - - for match in matches { - - let nsBody = body as NSString - let range = match.rangeAt(1) - let encodedLyrics = nsBody.substring(with: range) - - let decodedLyrics = decodeLyrics(encodedLyrics) - - completionHandler(decodedLyrics) - return - } - } - - completionHandler(nil) - } - - // Escapes the HTML entities - private func decodeLyrics(_ lyrics: String) -> String { - - let unescapedLyrics = lyrics.htmlUnescape() - return unescapedLyrics.replacingOccurrences(of: "
", with: "\n") - } -} diff --git a/Sources/main.swift b/Sources/main.swift deleted file mode 100644 index 9d46e92..0000000 --- a/Sources/main.swift +++ /dev/null @@ -1,151 +0,0 @@ -import CommandLineKit -import Foundation - -// Entry point of the application. This is the main executable -private func main() { - let (flags, parser) = createParser() - - do { - try parser.parse() - } catch { - parser.printUsage(error) - exit(EX_USAGE) - } - - // Boolean Options - - checkHelpFlag(flags["help"], withParser: parser) - checkVersionFlag(flags["version"], withParser: parser) - checkListSourcesFlag(flags["listSources"], withParser: parser) - checkTitleFlag(flags["title"], withParser: parser) - - // String Options - - checkEnableSourceFlag(flags["enableSource"], withParser: parser) - checkDisableSourceFlag(flags["disableSource"], withParser: parser) - checkResetSourceFlag(flags["resetSource"], withParser: parser) - - // Remove any flags so anyone after this gets the unprocessed values - - let programName: [String] = [CommandLine.arguments[0]] - CommandLine.arguments = programName + parser.unparsedArguments - - // Run Lyricli - - Lyricli.printLyrics() -} - -/// Sets up and returns a new options parser -/// -/// - Returns: A Dictionary of Options, and a new CommandLineKit instance -private func createParser() -> ([String:Option], CommandLineKit) { - let parser = CommandLineKit() - var flags: [String:Option] = [:] - - flags["help"] = BoolOption(shortFlag: "h", longFlag: "help", helpMessage: "Prints a help message.") - flags["version"] = BoolOption(shortFlag: "v", longFlag: "version", helpMessage: "Prints the version.") - - flags["enableSource"] = StringOption(shortFlag: "e", longFlag: "enable-source", helpMessage: "Enables a source") - flags["disableSource"] = StringOption(shortFlag: "d", longFlag: "disable-source", helpMessage: "Disables a source") - flags["resetSource"] = StringOption(shortFlag: "r", longFlag: "reset-source", helpMessage: "Resets a source") - flags["listSources"] = BoolOption(shortFlag: "l", longFlag: "list-sources", helpMessage: "Lists all sources") - - flags["title"] = BoolOption(shortFlag: "t", longFlag: "title", helpMessage: "Shows title of song if true") - - parser.addOptions(Array(flags.values)) - - parser.formatOutput = {parseString, type in - - var formattedString: String - - switch type { - case .About: - formattedString = "\(parseString) [ ]" - break - default: - formattedString = parseString - } - - return parser.defaultFormat(formattedString, type: type) - } - - return (flags, parser) -} - -// Handle the Help flag - -private func checkHelpFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let helpFlag = flag as? BoolOption { - if helpFlag.value { - parser.printUsage() - exit(0) - } - } -} - -// Handle the version flag - -private func checkVersionFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let versionFlag = flag as? BoolOption { - if versionFlag.value { - print(Lyricli.version) - exit(0) - } - } -} - -// Handle the list sources flag - -private func checkListSourcesFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let listSourcesFlag = flag as? BoolOption { - if listSourcesFlag.value { - Lyricli.printSources() - exit(0) - } - } -} - -// Handle the title flag - -private func checkTitleFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let titleFlag = flag as? BoolOption { - if titleFlag.value { - Lyricli.showTitle = true - } - } -} - -// Handle the enable source flag - -private func checkEnableSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let enableSourceFlag = flag as? StringOption { - if let source = enableSourceFlag.value { - Lyricli.enableSource(source) - exit(0) - } - } -} - -// Handle the disable source flag - -private func checkDisableSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let disableSourceFlag = flag as? StringOption { - if let source = disableSourceFlag.value { - Lyricli.disableSource(source) - exit(0) - } - } -} - -// Handle the reset source flag - -private func checkResetSourceFlag(_ flag: Option?, withParser parser: CommandLineKit) { - if let resetSourceFlag = flag as? StringOption { - if let source = resetSourceFlag.value { - Lyricli.resetSource(source) - exit(0) - } - } -} - -main() diff --git a/Sources/source_manager.swift b/Sources/source_manager.swift deleted file mode 100644 index e6c42da..0000000 --- a/Sources/source_manager.swift +++ /dev/null @@ -1,52 +0,0 @@ -// Collect and manage the available and enabled source -class SourceManager { - - // List of sources enabled for the crurent platform - private var availableSources: [String: Source] = [ - "arguments": ArgumentsSource(), - "itunes": ItunesSource(), - "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/source_protocol.swift b/Sources/source_protocol.swift deleted file mode 100644 index 0885994..0000000 --- a/Sources/source_protocol.swift +++ /dev/null @@ -1,5 +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 } -} diff --git a/Sources/spotify_source.swift b/Sources/spotify_source.swift deleted file mode 100644 index 2e56c8e..0000000 --- a/Sources/spotify_source.swift +++ /dev/null @@ -1,41 +0,0 @@ -import ScriptingBridge - -// Protocol to obtain the track from Spotify -@objc protocol SpotifyTrack { - @objc optional var name: String {get} - @objc optional var artist: String {get} -} - -// Protocol to interact with Spotify -@objc protocol SpotifyApplication { - @objc optional var currentTrack: SpotifyTrack? {get} -} - -extension SBApplication : SpotifyApplication {} - -// Source that reads track artist and name from current Spotify track -class SpotifySource: Source { - - // Calls the spotify API and returns the current track - var currentTrack: Track? { - - if let spotify: SpotifyApplication = SBApplication(bundleIdentifier: "com.spotify.client") { - - // Attempt to fetch the title from a song - if let currentTrack = spotify.currentTrack { - if let track = currentTrack { - if let name = track.name { - if let artist = track.artist { - - return Track(withName: name, andArtist: artist) - } - } - } - } - } - - return nil - } - -} - diff --git a/Sources/track.swift b/Sources/track.swift deleted file mode 100644 index ead4359..0000000 --- a/Sources/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 - } -} -- cgit