Search lyrics or song metadata from your terminal
genius genius-lyrics-search genius-lyrics cli rust
at master 230 lines 7.6 kB view raw
1use std::{env, process::exit}; 2 3use clap::{Arg, Command}; 4use colored_json::ToColoredJson; 5use genius_rust::Genius; 6use owo_colors::{ 7 colors::{css::Orange, Black, BrightGreen, BrightYellow, Cyan, Magenta, Yellow}, 8 OwoColorize, 9}; 10use rand::Rng; 11use serde::Deserialize; 12 13// API response structure from https://genius-mcp.xvzf.workers.dev/api/song/{id}/lyrics 14// All fields are part of the API response and need to be present for deserialization 15// Some fields aren't directly used in the code but are required for proper JSON parsing 16#[allow(dead_code)] 17#[derive(Debug, Deserialize)] 18struct LyricsApiResponse { 19 #[serde(default)] 20 id: u32, 21 title: String, 22 #[serde(default)] 23 artist_names: String, // Alternative to primary_artist.name 24 url: String, 25 lyrics: String, 26 primary_artist: ApiPrimaryArtist, 27} 28 29#[allow(dead_code)] 30#[derive(Debug, Deserialize)] 31struct ApiPrimaryArtist { 32 #[serde(default)] 33 id: u32, 34 name: String, 35 url: String, 36} 37 38fn print_song_info(artist_name: &str, title: &str, url: &str) { 39 let mut rng = rand::thread_rng(); 40 match rng.gen_range(0..6) { 41 0 => { 42 println!( 43 "\n{}{}{}", 44 artist_name.fg::<Black>().bg::<Magenta>(), 45 " - ".fg::<Black>().bg::<Magenta>(), 46 title.fg::<Black>().bg::<Magenta>() 47 ); 48 println!("{}\n", url.fg::<Magenta>()); 49 } 50 1 => { 51 println!( 52 "\n{}{}{}", 53 artist_name.fg::<Black>().bg::<Cyan>(), 54 " - ".fg::<Black>().bg::<Cyan>(), 55 title.fg::<Black>().bg::<Cyan>() 56 ); 57 println!("{}\n", url.fg::<Cyan>()); 58 } 59 2 => { 60 println!( 61 "\n{}{}{}", 62 artist_name.fg::<Black>().bg::<Orange>(), 63 " - ".fg::<Black>().bg::<Orange>(), 64 title.fg::<Black>().bg::<Orange>() 65 ); 66 println!("{}\n", url.fg::<Orange>()); 67 } 68 3 => { 69 println!( 70 "\n{}{}{}", 71 artist_name.fg::<Black>().bg::<BrightGreen>(), 72 " - ".fg::<Black>().bg::<BrightGreen>(), 73 title.fg::<Black>().bg::<BrightGreen>() 74 ); 75 println!("{}\n", url.fg::<BrightGreen>()); 76 } 77 4 => { 78 println!( 79 "\n{}{}{}", 80 artist_name.fg::<Black>().bg::<Yellow>(), 81 " - ".fg::<Black>().bg::<Yellow>(), 82 title.fg::<Black>().bg::<Yellow>() 83 ); 84 println!("{}\n", url.fg::<Yellow>()); 85 } 86 5 => { 87 println!( 88 "\n{}{}{}", 89 artist_name.fg::<Black>().bg::<BrightYellow>(), 90 " - ".fg::<Black>().bg::<BrightYellow>(), 91 title.fg::<Black>().bg::<BrightYellow>() 92 ); 93 println!("{}\n", url.fg::<BrightYellow>()); 94 } 95 _ => { 96 println!( 97 "\n{}{}{}", 98 artist_name.fg::<Black>().bg::<Magenta>(), 99 " - ".fg::<Black>().bg::<Magenta>(), 100 title.fg::<Black>().bg::<Magenta>() 101 ); 102 println!("{}\n", url.fg::<Magenta>()); 103 } 104 } 105} 106 107fn cli() -> Command<'static> { 108 const VERSION: &str = env!("CARGO_PKG_VERSION"); 109 Command::new("genius") 110 .version(VERSION) 111 .author("Tsiry Sandratraina <tsiry.sndr@aol.com>") 112 .about( 113 r#" 114 ______ _ ________ ____ 115 / ____/__ ____ (_)_ _______ / ____/ / / _/ 116 / / __/ _ \/ __ \/ / / / / ___/ / / / / / / 117 / /_/ / __/ / / / / /_/ (__ ) / /___/ /____/ / 118 \____/\___/_/ /_/_/\__,_/____/ \____/_____/___/ 119 120 Genius CLI helps you search for lyrics or song informations from Genius.com. 121 122"#, 123 ) 124 .subcommand_required(true) 125 .subcommand( 126 Command::new("search").about("Search for a song").arg( 127 Arg::with_name("query") 128 .help("The query to search for, e.g. 'Niu Raza Any E'") 129 .required(true) 130 .index(1), 131 ), 132 ) 133 .subcommand( 134 Command::new("lyrics") 135 .about("Get the lyrics of a song") 136 .arg( 137 Arg::with_name("id") 138 .help("The id of the song, e.g. '8333752'") 139 .required(true) 140 .index(1), 141 ), 142 ) 143 .subcommand( 144 Command::new("song").about("Get song informations").arg( 145 Arg::with_name("id") 146 .help("The id of the song, e.g. '8333752'") 147 .required(true) 148 .index(1), 149 ), 150 ) 151} 152 153#[tokio::main] 154async fn main() { 155 if env::var("GENIUS_TOKEN").is_err() { 156 println!("Please set the GENIUS_TOKEN environment variable"); 157 exit(1); 158 } 159 160 let genius = Genius::new(env::var("GENIUS_TOKEN").unwrap()); 161 162 let matches = cli().get_matches(); 163 match matches.subcommand() { 164 Some(("search", sub_matches)) => { 165 let query = sub_matches.get_one::<String>("query").unwrap(); 166 let response = genius.search(query).await.unwrap(); 167 println!( 168 "{}", 169 serde_json::to_string(&response) 170 .unwrap() 171 .to_colored_json_auto() 172 .unwrap() 173 ); 174 } 175 Some(("lyrics", sub_matches)) => { 176 let id = sub_matches 177 .get_one::<String>("id") 178 .unwrap() 179 .parse::<u32>() 180 .unwrap(); 181 182 let url = format!("https://genius-mcp.xvzf.workers.dev/api/song/{}/lyrics", id); 183 let client = reqwest::Client::new(); 184 185 match client.get(&url).send().await { 186 Ok(response) => { 187 match response.json::<LyricsApiResponse>().await { 188 Ok(lyrics_data) => { 189 // Print song info using our helper function 190 print_song_info( 191 &lyrics_data.primary_artist.name, 192 &lyrics_data.title, 193 &lyrics_data.url 194 ); 195 196 // Print the lyrics 197 println!("{}", lyrics_data.lyrics); 198 } 199 Err(e) => { 200 eprintln!("Error parsing API response: {}", e); 201 eprintln!("Please check that the song ID exists."); 202 exit(1); 203 } 204 } 205 } 206 Err(e) => { 207 eprintln!("Error fetching lyrics from API: {}", e); 208 eprintln!("Please check your internet connection and try again."); 209 exit(1); 210 } 211 } 212 } 213 Some(("song", sub_matches)) => { 214 let id = sub_matches 215 .get_one::<String>("id") 216 .unwrap() 217 .parse::<u32>() 218 .unwrap(); 219 let song = genius.get_song(id, "plain").await.unwrap(); 220 println!( 221 "{}", 222 serde_json::to_string(&song) 223 .unwrap() 224 .to_colored_json_auto() 225 .unwrap() 226 ); 227 } 228 _ => unreachable!(), 229 } 230}