Browse and listen to thousands of radio stations across the globe right from your terminal ๐ ๐ป ๐ตโจ
radio
rust
tokio
web-radio
command-line-tool
tui
1use std::time::Duration;
2
3use anyhow::Error;
4use serde::Deserialize;
5use surf::{Client, Config, Url};
6
7#[derive(Deserialize)]
8pub struct Header {
9 #[serde(rename = "Title")]
10 pub title: String,
11 #[serde(rename = "Subtitle")]
12 pub subtitle: String,
13}
14
15#[derive(Deserialize)]
16pub struct NowPlaying {
17 #[serde(rename = "Header")]
18 pub header: Header,
19}
20
21pub async fn extract_stream_url(url: &str, playlist_type: Option<String>) -> Result<String, Error> {
22 match playlist_type {
23 Some(playlist_type) => match playlist_type.as_str() {
24 "pls" => {
25 let client: Client = Config::new()
26 .set_timeout(Some(Duration::from_secs(5)))
27 .try_into()
28 .unwrap();
29 let response = client
30 .get(Url::parse(url)?)
31 .recv_string()
32 .await
33 .map_err(|e| Error::msg(e.to_string()))?;
34
35 let mut response = response.replace("[Playlist]", "[playlist]");
36 if !response.contains("NumberOfEntries") {
37 response = format!("{}\nNumberOfEntries=1", response);
38 }
39
40 let url = pls::parse(&mut response.as_bytes())
41 .map_err(|e| Error::msg(e.to_string()))?
42 .first()
43 .map(|e| e.path.clone())
44 .unwrap();
45 Ok(url.to_string())
46 }
47 _ => Err(Error::msg(format!(
48 "Playlist type {} not supported",
49 playlist_type
50 ))),
51 },
52 None => Ok(url.to_string()),
53 }
54}
55
56pub async fn get_currently_playing(station: &str) -> Result<String, Error> {
57 let client = Client::new();
58 let url = format!(
59 "https://feed.tunein.com/profiles/{}/nowPlaying?partnerId=RadioTime",
60 station
61 );
62 let response: NowPlaying = client
63 .get(Url::parse(&url)?)
64 .recv_json()
65 .await
66 .map_err(|e| Error::msg(e.to_string()))?;
67
68 let subtitle = response.header.subtitle.trim();
69 if subtitle.is_empty() {
70 Ok(response.header.title.trim().to_string())
71 } else {
72 Ok(subtitle.to_string())
73 }
74}