tunein-cli 0.5.0

Browse and listen to thousands of radio stations across the globe right from your terminal 🌎 📻 🎵✨
use crate::types::Station;

use super::Provider;
use anyhow::Error;
use async_trait::async_trait;
use tunein::TuneInClient;

pub struct Tunein {
    client: TuneInClient,
}

impl Tunein {
    pub fn new() -> Self {
        Self {
            client: TuneInClient::new(),
        }
    }
}

#[async_trait]
impl Provider for Tunein {
    async fn search(&self, name: String) -> Result<Vec<Station>, Error> {
        let results = self
            .client
            .search(&name)
            .await
            .map_err(|e| Error::msg(e.to_string()))?;
        let stations = results.into_iter().map(|x| Station::from(x)).collect();
        Ok(stations)
    }

    async fn get_station(&self, id: String) -> Result<Option<Station>, Error> {
        let stations = self
            .client
            .get_station(&id)
            .await
            .map_err(|e| Error::msg(e.to_string()))?;
        match stations.len() {
            0 => {
                let results = self.search(id.clone()).await?;
                let station = results.first().cloned();
                match station {
                    Some(st) => {
                        let stations = self
                            .client
                            .get_station(&st.id)
                            .await
                            .map_err(|e| Error::msg(e.to_string()))?;
                        let mut station = Station::from(stations[0].clone());
                        station.id = st.id.clone();
                        station.name = st.name.clone();
                        station.playing = st.playing.clone();
                        return Ok(Some(station));
                    }
                    None => Ok(None),
                }
            }
            _ => {
                let mut station = Station::from(stations[0].clone());
                // Preserve the original station ID since StationLinkDetails doesn't contain it
                station.id = id;
                Ok(Some(station))
            }
        }
    }

    async fn browse(
        &self,
        category: String,
        _offset: u32,
        _limit: u32,
    ) -> Result<Vec<Station>, Error> {
        let guide_id = category.clone();
        let category = match category.to_lowercase().as_str() {
            "by location" => Some(tunein::types::Category::ByLocation),
            "by language" => Some(tunein::types::Category::ByLanguage),
            "sports" => Some(tunein::types::Category::Sports),
            "talk" => Some(tunein::types::Category::Talk),
            "music" => Some(tunein::types::Category::Music),
            "local radio" => Some(tunein::types::Category::LocalRadio),
            "podcasts" => Some(tunein::types::Category::Podcasts),
            _ => None,
        };

        if category.is_none() {
            let category_stations = self
                .client
                .browse_by_id(&guide_id)
                .await
                .map_err(|e| Error::msg(e.to_string()))?;

            let mut stations = vec![];

            for st in category_stations {
                if let Some(children) = st.clone().children {
                    stations = [stations, vec![Box::new(st.clone())], children].concat();
                }
            }

            let stations = stations.into_iter().map(|x| Station::from(x)).collect();
            return Ok(stations);
        }

        let category_stations = self
            .client
            .browse(category)
            .await
            .map_err(|e| Error::msg(e.to_string()))?;

        let stations = category_stations
            .clone()
            .into_iter()
            .map(|x| Station::from(x))
            .collect::<Vec<Station>>();

        let mut _stations = vec![];
        for st in category_stations {
            if let Some(children) = st.children {
                _stations = [_stations, children].concat();
            }
        }
        let _stations = _stations
            .into_iter()
            .map(|x| Station::from(x))
            .collect::<Vec<Station>>();

        Ok([stations, _stations].concat())
    }

    async fn categories(&self, _offset: u32, _limit: u32) -> Result<Vec<String>, Error> {
        let categories = self
            .client
            .browse(None)
            .await
            .map_err(|e| Error::msg(e.to_string()))?;
        let categories = categories.into_iter().map(|x| x.text).collect();
        Ok(categories)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    pub async fn test_search() {
        let provider = Tunein::new();
        let name = "alternativeradio";
        let stations = provider.search(name.to_string()).await.unwrap();
        println!("Search: {:#?}", stations);
        assert!(stations.len() > 0)
    }

    #[tokio::test]
    pub async fn test_get_station() {
        let provider = Tunein::new();
        let name = "s288303".to_string();
        let station = provider.get_station(name).await.unwrap();
        println!("Station: {:#?}", station);
        assert!(station.is_some())
    }

    #[tokio::test]
    pub async fn test_browse() {
        let provider = Tunein::new();
        let stations = provider.browse("music".to_string(), 0, 100).await.unwrap();
        println!("Browse: {:#?}", stations);
        assert!(stations.len() > 0)
    }

    #[tokio::test]
    pub async fn test_browse_by_id() {
        let provider = Tunein::new();
        let stations = provider.browse("c57942".to_string(), 0, 100).await.unwrap();
        println!("Browse by category id: {:#?}", stations);
        assert!(stations.len() > 0)
    }

    #[tokio::test]
    pub async fn test_categories() {
        let provider = Tunein::new();
        let categories = provider.categories(0, 100).await.unwrap();
        println!("Categories: {:#?}", categories);
        assert!(categories.len() > 0)
    }
}