use std::{env, process::exit}; use clap::{Arg, Command}; use colored_json::ToColoredJson; use genius_rust::Genius; use owo_colors::{ colors::{css::Orange, Black, BrightGreen, BrightYellow, Cyan, Magenta, Yellow}, OwoColorize, }; use rand::Rng; use serde::Deserialize; // API response structure from https://genius-mcp.xvzf.workers.dev/api/song/{id}/lyrics // All fields are part of the API response and need to be present for deserialization // Some fields aren't directly used in the code but are required for proper JSON parsing #[allow(dead_code)] #[derive(Debug, Deserialize)] struct LyricsApiResponse { #[serde(default)] id: u32, title: String, #[serde(default)] artist_names: String, // Alternative to primary_artist.name url: String, lyrics: String, primary_artist: ApiPrimaryArtist, } #[allow(dead_code)] #[derive(Debug, Deserialize)] struct ApiPrimaryArtist { #[serde(default)] id: u32, name: String, url: String, } fn print_song_info(artist_name: &str, title: &str, url: &str) { let mut rng = rand::thread_rng(); match rng.gen_range(0..6) { 0 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } 1 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } 2 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } 3 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } 4 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } 5 => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } _ => { println!( "\n{}{}{}", artist_name.fg::().bg::(), " - ".fg::().bg::(), title.fg::().bg::() ); println!("{}\n", url.fg::()); } } } fn cli() -> Command<'static> { const VERSION: &str = env!("CARGO_PKG_VERSION"); Command::new("genius") .version(VERSION) .author("Tsiry Sandratraina ") .about( r#" ______ _ ________ ____ / ____/__ ____ (_)_ _______ / ____/ / / _/ / / __/ _ \/ __ \/ / / / / ___/ / / / / / / / /_/ / __/ / / / / /_/ (__ ) / /___/ /____/ / \____/\___/_/ /_/_/\__,_/____/ \____/_____/___/ Genius CLI helps you search for lyrics or song informations from Genius.com. "#, ) .subcommand_required(true) .subcommand( Command::new("search").about("Search for a song").arg( Arg::with_name("query") .help("The query to search for, e.g. 'Niu Raza Any E'") .required(true) .index(1), ), ) .subcommand( Command::new("lyrics") .about("Get the lyrics of a song") .arg( Arg::with_name("id") .help("The id of the song, e.g. '8333752'") .required(true) .index(1), ), ) .subcommand( Command::new("song").about("Get song informations").arg( Arg::with_name("id") .help("The id of the song, e.g. '8333752'") .required(true) .index(1), ), ) } #[tokio::main] async fn main() { if env::var("GENIUS_TOKEN").is_err() { println!("Please set the GENIUS_TOKEN environment variable"); exit(1); } let genius = Genius::new(env::var("GENIUS_TOKEN").unwrap()); let matches = cli().get_matches(); match matches.subcommand() { Some(("search", sub_matches)) => { let query = sub_matches.get_one::("query").unwrap(); let response = genius.search(query).await.unwrap(); println!( "{}", serde_json::to_string(&response) .unwrap() .to_colored_json_auto() .unwrap() ); } Some(("lyrics", sub_matches)) => { let id = sub_matches .get_one::("id") .unwrap() .parse::() .unwrap(); let url = format!("https://genius-mcp.xvzf.workers.dev/api/song/{}/lyrics", id); let client = reqwest::Client::new(); match client.get(&url).send().await { Ok(response) => { match response.json::().await { Ok(lyrics_data) => { // Print song info using our helper function print_song_info( &lyrics_data.primary_artist.name, &lyrics_data.title, &lyrics_data.url ); // Print the lyrics println!("{}", lyrics_data.lyrics); } Err(e) => { eprintln!("Error parsing API response: {}", e); eprintln!("Please check that the song ID exists."); exit(1); } } } Err(e) => { eprintln!("Error fetching lyrics from API: {}", e); eprintln!("Please check your internet connection and try again."); exit(1); } } } Some(("song", sub_matches)) => { let id = sub_matches .get_one::("id") .unwrap() .parse::() .unwrap(); let song = genius.get_song(id, "plain").await.unwrap(); println!( "{}", serde_json::to_string(&song) .unwrap() .to_colored_json_auto() .unwrap() ); } _ => unreachable!(), } }