aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2024-03-15 22:10:06 +0100
committerRuben Beltran del Rio <git@r.bdr.sh>2024-03-15 22:10:06 +0100
commit44e7b4de4073e6dc25681bb2fa6977bf5869689a (patch)
tree164c8d8d980732e64d96fe6e788373108144d085 /Sources
parentb2096fb3958d9e9cdcca77931aadfb947cab24ee (diff)
Add macos sources
Diffstat (limited to 'Sources')
-rw-r--r--Sources/lyricli/configuration.swift70
-rw-r--r--Sources/lyricli/errors/configuration_could_not_be_read.swift3
-rw-r--r--Sources/lyricli/errors/source_could_not_be_disabled.swift6
-rw-r--r--Sources/lyricli/errors/source_could_not_be_enabled.swift6
-rw-r--r--Sources/lyricli/errors/source_could_not_be_reset.swift6
-rw-r--r--Sources/lyricli/errors/source_not_available.swift3
-rw-r--r--Sources/lyricli/lyricli.swift102
-rw-r--r--Sources/lyricli/lyricli_command.swift95
-rw-r--r--Sources/lyricli/lyrics_engine.swift142
-rw-r--r--Sources/lyricli/source_manager.swift51
-rw-r--r--Sources/lyricli/sources/apple_music_source.swift80
-rw-r--r--Sources/lyricli/sources/source_protocol.swift9
-rw-r--r--Sources/lyricli/track.swift15
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
- }
-}