Browse and listen to thousands of radio stations across the globe right from your terminal 🌎 📻 🎵✨
radio rust tokio web-radio command-line-tool tui
at main 186 lines 5.9 kB view raw
1use crate::types::Station; 2 3use super::Provider; 4use anyhow::Error; 5use async_trait::async_trait; 6use tunein::TuneInClient; 7 8pub struct Tunein { 9 client: TuneInClient, 10} 11 12impl Tunein { 13 pub fn new() -> Self { 14 Self { 15 client: TuneInClient::new(), 16 } 17 } 18} 19 20#[async_trait] 21impl Provider for Tunein { 22 async fn search(&self, name: String) -> Result<Vec<Station>, Error> { 23 let results = self 24 .client 25 .search(&name) 26 .await 27 .map_err(|e| Error::msg(e.to_string()))?; 28 let stations = results.into_iter().map(|x| Station::from(x)).collect(); 29 Ok(stations) 30 } 31 32 async fn get_station(&self, id: String) -> Result<Option<Station>, Error> { 33 let stations = self 34 .client 35 .get_station(&id) 36 .await 37 .map_err(|e| Error::msg(e.to_string()))?; 38 match stations.len() { 39 0 => { 40 let results = self.search(id.clone()).await?; 41 let station = results.first().cloned(); 42 match station { 43 Some(st) => { 44 let stations = self 45 .client 46 .get_station(&st.id) 47 .await 48 .map_err(|e| Error::msg(e.to_string()))?; 49 let mut station = Station::from(stations[0].clone()); 50 station.id = st.id.clone(); 51 station.name = st.name.clone(); 52 station.playing = st.playing.clone(); 53 return Ok(Some(station)); 54 } 55 None => Ok(None), 56 } 57 } 58 _ => { 59 let mut station = Station::from(stations[0].clone()); 60 // Preserve the original station ID since StationLinkDetails doesn't contain it 61 station.id = id; 62 Ok(Some(station)) 63 } 64 } 65 } 66 67 async fn browse( 68 &self, 69 category: String, 70 _offset: u32, 71 _limit: u32, 72 ) -> Result<Vec<Station>, Error> { 73 let guide_id = category.clone(); 74 let category = match category.to_lowercase().as_str() { 75 "by location" => Some(tunein::types::Category::ByLocation), 76 "by language" => Some(tunein::types::Category::ByLanguage), 77 "sports" => Some(tunein::types::Category::Sports), 78 "talk" => Some(tunein::types::Category::Talk), 79 "music" => Some(tunein::types::Category::Music), 80 "local radio" => Some(tunein::types::Category::LocalRadio), 81 "podcasts" => Some(tunein::types::Category::Podcasts), 82 _ => None, 83 }; 84 85 if category.is_none() { 86 let category_stations = self 87 .client 88 .browse_by_id(&guide_id) 89 .await 90 .map_err(|e| Error::msg(e.to_string()))?; 91 92 let mut stations = vec![]; 93 94 for st in category_stations { 95 if let Some(children) = st.clone().children { 96 stations = [stations, vec![Box::new(st.clone())], children].concat(); 97 } 98 } 99 100 let stations = stations.into_iter().map(|x| Station::from(x)).collect(); 101 return Ok(stations); 102 } 103 104 let category_stations = self 105 .client 106 .browse(category) 107 .await 108 .map_err(|e| Error::msg(e.to_string()))?; 109 110 let stations = category_stations 111 .clone() 112 .into_iter() 113 .map(|x| Station::from(x)) 114 .collect::<Vec<Station>>(); 115 116 let mut _stations = vec![]; 117 for st in category_stations { 118 if let Some(children) = st.children { 119 _stations = [_stations, children].concat(); 120 } 121 } 122 let _stations = _stations 123 .into_iter() 124 .map(|x| Station::from(x)) 125 .collect::<Vec<Station>>(); 126 127 Ok([stations, _stations].concat()) 128 } 129 130 async fn categories(&self, _offset: u32, _limit: u32) -> Result<Vec<String>, Error> { 131 let categories = self 132 .client 133 .browse(None) 134 .await 135 .map_err(|e| Error::msg(e.to_string()))?; 136 let categories = categories.into_iter().map(|x| x.text).collect(); 137 Ok(categories) 138 } 139} 140 141#[cfg(test)] 142mod tests { 143 use super::*; 144 145 #[tokio::test] 146 pub async fn test_search() { 147 let provider = Tunein::new(); 148 let name = "alternativeradio"; 149 let stations = provider.search(name.to_string()).await.unwrap(); 150 println!("Search: {:#?}", stations); 151 assert!(stations.len() > 0) 152 } 153 154 #[tokio::test] 155 pub async fn test_get_station() { 156 let provider = Tunein::new(); 157 let name = "s288303".to_string(); 158 let station = provider.get_station(name).await.unwrap(); 159 println!("Station: {:#?}", station); 160 assert!(station.is_some()) 161 } 162 163 #[tokio::test] 164 pub async fn test_browse() { 165 let provider = Tunein::new(); 166 let stations = provider.browse("music".to_string(), 0, 100).await.unwrap(); 167 println!("Browse: {:#?}", stations); 168 assert!(stations.len() > 0) 169 } 170 171 #[tokio::test] 172 pub async fn test_browse_by_id() { 173 let provider = Tunein::new(); 174 let stations = provider.browse("c57942".to_string(), 0, 100).await.unwrap(); 175 println!("Browse by category id: {:#?}", stations); 176 assert!(stations.len() > 0) 177 } 178 179 #[tokio::test] 180 pub async fn test_categories() { 181 let provider = Tunein::new(); 182 let categories = provider.categories(0, 100).await.unwrap(); 183 println!("Categories: {:#?}", categories); 184 assert!(categories.len() > 0) 185 } 186}