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