tunein-cli 0.4.2

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

use super::{is_valid_uuid, Provider};
use anyhow::Error;
use async_trait::async_trait;
use radiobrowser::{ApiStation, RadioBrowserAPI};
use std::process::exit;

pub struct Radiobrowser {
    client: RadioBrowserAPI,
}

impl Radiobrowser {
    pub async fn new() -> Self {
        let client = RadioBrowserAPI::new().await;

        if client.is_err() {
            eprintln!("Failed to create a RadioBrowserAPI client");
            exit(1);
        }

        let client = client.unwrap();
        Self { client }
    }
}

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

    async fn get_station(&self, name_or_uuid: String) -> Result<Option<Station>, Error> {
        match is_valid_uuid(&name_or_uuid) {
            true => {
                let servers = RadioBrowserAPI::get_default_servers()
                    .await
                    .map_err(|e| anyhow::anyhow!(format!("{}", e)))?;

                if servers.is_empty() {
                    return Ok(None);
                }

                let client = reqwest::Client::new();
                let url = format!(
                    "https://{}/json/stations/byuuid/{}",
                    servers[0], name_or_uuid
                );
                let results = client
                    .get(&url)
                    .send()
                    .await?
                    .json::<Vec<ApiStation>>()
                    .await?;

                Ok(results.into_iter().next().map(|x| Station::from(x)))
            }
            false => {
                let stations = self
                    .client
                    .get_stations()
                    .name(&name_or_uuid)
                    .name_exact(true)
                    .send()
                    .await
                    .map_err(|e| anyhow::anyhow!(format!("{}", e)))?;
                match stations.len() {
                    0 => Ok(None),
                    _ => Ok(Some(Station::from(stations[0].clone()))),
                }
            }
        }
    }

    async fn browse(
        &self,
        category: String,
        offset: u32,
        limit: u32,
    ) -> Result<Vec<Station>, Error> {
        let stations = self
            .client
            .get_stations()
            .tag(&category)
            .offset(&format!("{}", offset))
            .limit(&format!("{}", limit))
            .send()
            .await
            .map_err(|e| anyhow::anyhow!(format!("{}", e)))?;
        let stations = stations.into_iter().map(|x| Station::from(x)).collect();
        Ok(stations)
    }

    async fn categories(&self, offset: u32, limit: u32) -> Result<Vec<String>, Error> {
        let categories = self
            .client
            .get_tags()
            .offset(&format!("{}", offset))
            .limit(&format!("{}", limit))
            .send()
            .await
            .map_err(|e| anyhow::anyhow!(format!("{}", e)))?;
        let categories = categories.into_iter().map(|x| x.name).collect();
        Ok(categories)
    }
}

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

    #[tokio::test]
    pub async fn test_search() {
        let provider = Radiobrowser::new().await;
        let name = "alternativeradio";
        let stations = provider.search(name.to_string()).await.unwrap();
        assert!(stations.len() == 1)
    }

    #[tokio::test]
    pub async fn test_get_station() {
        let provider = Radiobrowser::new().await;
        let name = "AlternativeRadio.us".to_string();
        let station = provider.get_station(name).await.unwrap();
        assert!(station.is_some())
    }

    #[tokio::test]
    pub async fn test_get_station_by_uuid() {
        let provider = Radiobrowser::new().await;
        let name = "964da563-0601-11e8-ae97-52543be04c81".to_string();
        let station = provider.get_station(name).await.unwrap();
        assert!(station.is_some())
    }

    #[tokio::test]
    pub async fn test_browse() {
        let provider = Radiobrowser::new().await;
        let stations = provider.browse("music".to_string(), 0, 100).await.unwrap();
        let stations = stations
            .into_iter()
            .map(|x| Station::from(x))
            .collect::<Vec<Station>>();
        assert!(stations.len() == 100)
    }

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