Search lyrics or song metadata from your terminal
genius
genius-lyrics-search
genius-lyrics
cli
rust
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}