]>
Commit | Line | Data |
---|---|---|
1 | use std::io::{Result, Error, ErrorKind::Other}; | |
2 | use serde::Deserialize; | |
3 | use reqwest::get; | |
4 | use scraper::{ElementRef, Html, Selector, node::Node::{Text, Element}}; | |
5 | ||
6 | #[derive(Deserialize, Debug)] | |
7 | struct GeniusApiResponse { | |
8 | response: GeniusResponseBlock, | |
9 | } | |
10 | ||
11 | #[derive(Deserialize, Debug)] | |
12 | struct GeniusResponseBlock { | |
13 | hits: Vec<GeniusHit>, | |
14 | } | |
15 | ||
16 | #[derive(Deserialize, Debug)] | |
17 | struct GeniusHit { | |
18 | #[serde(rename = "type")] | |
19 | hit_type: String, | |
20 | result: GeniusResult, | |
21 | } | |
22 | ||
23 | #[derive(Deserialize, Debug)] | |
24 | struct GeniusResult { | |
25 | url: String, | |
26 | } | |
27 | ||
28 | pub async fn search(url: &str) -> Result<String> { | |
29 | let response = get(url).await | |
30 | .map_err(|_| Error::new(Other, "Could not perform lyrics search in engine."))? | |
31 | .json::<GeniusApiResponse>().await | |
32 | .map_err(|_| Error::new(Other, "Lyrics engine returned invalid response from search."))?; | |
33 | let url = response.response.hits.into_iter() | |
34 | .find(|hit| hit.hit_type == "song") | |
35 | .map(|hit| hit.result.url) | |
36 | .ok_or_else(|| Error::new(Other, "Could not find a matching track in lyrics engine."))?; | |
37 | ||
38 | Ok(url) | |
39 | } | |
40 | ||
41 | pub async fn get_lyrics(url: &str) -> Result<String> { | |
42 | let song_html = get(url).await | |
43 | .map_err(|_| Error::new(Other, "Could not fetch lyrics from engine."))? | |
44 | .text().await | |
45 | .map_err(|_| Error::new(Other, "Lyrics engine returned invalid response."))?; | |
46 | ||
47 | let document = Html::parse_document(&song_html); | |
48 | let selector = Selector::parse(r#"div[data-lyrics-container="true"]"#).unwrap(); | |
49 | let lyrics_div = document.select(&selector).next() | |
50 | .ok_or_else(|| Error::new(Other, "Could not find lyrics in response."))?; | |
51 | ||
52 | let mut lyrics = String::new(); | |
53 | for node in lyrics_div.children() { | |
54 | match node.value() { | |
55 | Element(element) => { | |
56 | if element.name() == "br" { | |
57 | lyrics.push('\n'); | |
58 | } else { | |
59 | if let Some(element_ref) = ElementRef::wrap(node) { | |
60 | let text = element_ref.text().collect::<Vec<_>>().join(""); | |
61 | lyrics.push_str(&text); | |
62 | } | |
63 | } | |
64 | }, | |
65 | Text(text) => { | |
66 | lyrics.push_str(text); | |
67 | }, | |
68 | _ => {} | |
69 | } | |
70 | } | |
71 | ||
72 | Ok(lyrics) | |
73 | } |