+use serde::{Deserialize, Serialize};
+use serde_json;
use std::env;
use std::fs::{create_dir_all, write, File};
use std::io::{Read, Result};
use std::path::PathBuf;
-use serde::{Deserialize, Serialize};
-use serde_json;
const CONFIG_ENV_VARIABLE: &str = "LYRICLI_CONFIG_DIRECTORY";
const CONFIG_DEFAULT_LOCATION: &str = "XDG_CONFIG_HOME";
#[derive(Serialize, Deserialize, Debug)]
pub struct Configuration {
- enabled_sources: Vec<String>
+ enabled_sources: Vec<String>,
}
impl Configuration {
-
- pub fn new() -> Self {
-
+ pub fn new() -> Self {
if let Some(configuration) = Configuration::read() {
return configuration;
}
enabled_sources: vec![
"apple_music".to_string(),
"spotify".to_string(),
- "strawberry".to_string()
- ]
+ "strawberry".to_string(),
+ ],
}
}
Ok(directory) => PathBuf::from(directory),
Err(_) => match env::var("HOME") {
Ok(directory) => PathBuf::from(directory).join(CONFIG_FALLBACK_LOCATION),
- Err(_) => panic!("Could not find required directory, {} or {} should be set and readable.", CONFIG_ENV_VARIABLE, CONFIG_DEFAULT_LOCATION),
+ Err(_) => panic!(
+ "Could not find required directory, {} or {} should be set and readable.",
+ CONFIG_ENV_VARIABLE, CONFIG_DEFAULT_LOCATION
+ ),
},
},
- }.join(CONFIG_SUBDIRECTORY)
+ }
+ .join(CONFIG_SUBDIRECTORY)
}
}
-use std::io::{Result, Error, ErrorKind::Other};
-use serde::Deserialize;
use reqwest::get;
-use scraper::{ElementRef, Html, Selector, node::Node::{Text, Element}};
+use scraper::{
+ node::Node::{Element, Text},
+ ElementRef, Html, Selector,
+};
+use serde::Deserialize;
+use std::io::{Error, ErrorKind::Other, Result};
#[derive(Deserialize, Debug)]
struct GeniusApiResponse {
}
pub async fn search(url: &str) -> Result<String> {
- let response = get(url).await
+ let response = get(url)
+ .await
.map_err(|_| Error::new(Other, "Could not perform lyrics search in engine."))?
- .json::<GeniusApiResponse>().await
- .map_err(|_| Error::new(Other, "Lyrics engine returned invalid response from search."))?;
- let url = response.response.hits.into_iter()
+ .json::<GeniusApiResponse>()
+ .await
+ .map_err(|_| {
+ Error::new(
+ Other,
+ "Lyrics engine returned invalid response from search.",
+ )
+ })?;
+ let url = response
+ .response
+ .hits
+ .into_iter()
.find(|hit| hit.hit_type == "song")
.map(|hit| hit.result.url)
.ok_or_else(|| Error::new(Other, "Could not find a matching track in lyrics engine."))?;
}
pub async fn get_lyrics(url: &str) -> Result<String> {
- let song_html = get(url).await
+ let song_html = get(url)
+ .await
.map_err(|_| Error::new(Other, "Could not fetch lyrics from engine."))?
- .text().await
+ .text()
+ .await
.map_err(|_| Error::new(Other, "Lyrics engine returned invalid response."))?;
let document = Html::parse_document(&song_html);
let selector = Selector::parse(r#"div[data-lyrics-container="true"]"#).unwrap();
- let lyrics_div = document.select(&selector).next()
+ let lyrics_div = document
+ .select(&selector)
+ .next()
.ok_or_else(|| Error::new(Other, "Could not find lyrics in response."))?;
let mut lyrics = String::new();
lyrics.push_str(&text);
}
}
- },
+ }
Text(text) => {
lyrics.push_str(text);
- },
+ }
_ => {}
}
}
-use std::io::Result;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
+use std::io::Result;
mod genius;
let artist = utf8_percent_encode(&track.artist, NON_ALPHANUMERIC).to_string();
let name = utf8_percent_encode(&track.name, NON_ALPHANUMERIC).to_string();
- let url = format!("{}?access_token={}&q={}%20{}", GENIUS_API_URL, GENIUS_CLIENT_TOKEN, artist, name);
+ let url = format!(
+ "{}?access_token={}&q={}%20{}",
+ GENIUS_API_URL, GENIUS_CLIENT_TOKEN, artist, name
+ );
let song_url = search(&url).await?;
let lyrics = get_lyrics(&song_url).await?;
mod lyrics_engine;
mod sources;
-use std::io::{Result, Error, ErrorKind::Other};
use clap::Parser;
+use std::io::{Error, ErrorKind::Other, Result};
use configuration::Configuration;
-use sources::{enable, disable, get_track, reset, list};
use lyrics_engine::print_lyrics;
+use sources::{disable, enable, get_track, list, reset};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Arguments {
-
// Positional Arguments
/// Specify the artist.
artist: Option<String>,
pub struct Track {
pub name: String,
- pub artist: String
+ pub artist: String,
}
#[tokio::main]
if let Some(artist) = arguments.artist {
current_track = Track {
name: arguments.track_name.unwrap_or("".to_string()),
- artist: artist
+ artist: artist,
};
} else {
- current_track = get_track()
- .ok_or_else(|| Error::new(Other, "No Artist/Song could be found :("))?
+ current_track =
+ get_track().ok_or_else(|| Error::new(Other, "No Artist/Song could be found :("))?
}
print_lyrics(current_track, arguments.show_title).await
use std::ffi::CStr;
use std::io::Result;
-use cocoa::{base::{nil, id}, foundation::NSString};
-use objc::{class, msg_send, sel, sel_impl, runtime::{Class, Object}};
+use cocoa::{
+ base::{id, nil},
+ foundation::NSString,
+};
+use objc::{
+ class, msg_send,
+ runtime::{Class, Object},
+ sel, sel_impl,
+};
use objc_id::Id;
use crate::Track;
}
impl LyricsSource for AppleMusic {
-
fn name(&self) -> String {
"apple_music".to_string()
}
unsafe {
let app: Id<Object> = {
let cls = class!(SBApplication);
- let bundle_identifier = NSString::alloc(nil).init_str("com.apple.Music");
+ let bundle_identifier = NSString::alloc(nil).init_str("com.apple.Music");
let workspace_class = Class::get("NSWorkspace").unwrap();
let shared_workspace: id = msg_send![workspace_class, sharedWorkspace];
let app_url: id = msg_send![shared_workspace, URLForApplicationWithBundleIdentifier:bundle_identifier];
if app_url != nil {
- let app: *mut Object = msg_send![cls, applicationWithBundleIdentifier:bundle_identifier];
+ let app: *mut Object =
+ msg_send![cls, applicationWithBundleIdentifier:bundle_identifier];
Id::from_ptr(app)
} else {
- return None
+ return None;
}
};
let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
let artist = CStr::from_ptr(artist_ptr).to_string_lossy().into_owned();
-
- return Some(Track {
- name,
- artist
- })
+ return Some(Track { name, artist });
}
}
}
Ok(())
}
}
-
-
-use std::io::Result;
use mpris::PlayerFinder;
+use std::io::Result;
use crate::Track;
}
impl LyricsSource for Dbus {
-
fn name(&self) -> String {
"dbus".to_string()
}
fn current_track(&self) -> Option<Track> {
- let player = PlayerFinder::new().ok()?
- .find_active().ok()?;
+ let player = PlayerFinder::new().ok()?.find_active().ok()?;
let metadata = player.get_metadata().ok()?;
let name = metadata.title()?.to_string();
let artists = metadata.artists()?;
let artist = artists.get(0)?.to_string();
- Some(Track {
- name,
- artist
- })
+ Some(Track { name, artist })
}
fn disable(&self) -> Result<()> {
-use std::io::{Result, Error, ErrorKind::Other};
+use std::io::{Error, ErrorKind::Other, Result};
#[cfg(target_os = "macos")]
mod apple_music;
}
pub fn list() -> Vec<String> {
- available_sources().into_iter().map(|source| source.name()).collect()
+ available_sources()
+ .into_iter()
+ .map(|source| source.name())
+ .collect()
}
pub fn enable(source_name: &String) -> Result<()> {
let sources = available_sources();
for source in sources {
if &source.name() == source_name {
- return source.enable()
+ return source.enable();
}
}
Err(Error::new(Other, "No such source was available."))
let sources = available_sources();
for source in sources {
if &source.name() == source_name {
- return source.disable()
+ return source.disable();
}
}
Err(Error::new(Other, "No such source was available."))
let sources = available_sources();
for source in sources {
if &source.name() == source_name {
- return source.reset()
+ return source.reset();
}
}
Err(Error::new(Other, "No such source was available."))
return Some(track);
}
}
- return None
+ return None;
}
pub fn available_sources() -> Vec<Box<dyn LyricsSource>> {
use std::ffi::CStr;
use std::io::Result;
-use cocoa::{base::{nil, id}, foundation::NSString};
-use objc::{class, msg_send, sel, sel_impl, runtime::{Class, Object}};
+use cocoa::{
+ base::{id, nil},
+ foundation::NSString,
+};
+use objc::{
+ class, msg_send,
+ runtime::{Class, Object},
+ sel, sel_impl,
+};
use objc_id::Id;
use crate::Track;
}
impl LyricsSource for Spotify {
-
fn name(&self) -> String {
"spotify".to_string()
}
unsafe {
let app: Id<Object> = {
let cls = class!(SBApplication);
- let bundle_identifier = NSString::alloc(nil).init_str("com.spotify.Client");
+ let bundle_identifier = NSString::alloc(nil).init_str("com.spotify.Client");
let workspace_class = Class::get("NSWorkspace").unwrap();
let shared_workspace: id = msg_send![workspace_class, sharedWorkspace];
let app_url: id = msg_send![shared_workspace, URLForApplicationWithBundleIdentifier:bundle_identifier];
if app_url != nil {
- let app: *mut Object = msg_send![cls, applicationWithBundleIdentifier:bundle_identifier];
+ let app: *mut Object =
+ msg_send![cls, applicationWithBundleIdentifier:bundle_identifier];
Id::from_ptr(app)
} else {
- return None
+ return None;
}
};
let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
let artist = CStr::from_ptr(artist_ptr).to_string_lossy().into_owned();
-
- return Some(Track {
- name,
- artist
- })
+ return Some(Track { name, artist });
}
}
}