use std::vec; use poise::ChoiceParameter; use reqwest::Client; use songbird::input::Input; use songbird::input::HttpRequest; use super::super::connect; use super::link_or_string; use super::parse_radio_autocomplete; use super::LinkString; use crate::commands::voice_types::NumberOfEntries; use crate::commands::voice_types::PlayingInfoType; use crate::types::ContextExt; use crate::types::{Context, Error}; use crate::commands::voice::voice_utils::autocomplete_channel; use super::radio_utils::paginate_search_stations; use super::super::voice_utils::MAX_ENTRIES; #[poise::command( slash_command, description_localized("en-US", "Plays music from URL source"), subcommands("search", "play") )] pub async fn radio(_ctx: Context<'_>) -> Result<(), Error> { Ok(()) } // TODO: deduplicate you use a lot of the same code // TODO: autocomplete radio stream URLs #[poise::command( slash_command, description_localized("en-US", "Plays music from URL source") )] pub async fn play(ctx: Context<'_>, #[autocomplete = "autocomplete_channel"] #[description = "Voice channel name: "] channel: Option, #[autocomplete = "autocomplete_radio"] #[description = "Radio station: "] name: String, ) -> Result<(), Error> { if ctx.guild().is_none() { ctx.reply_ephemeral("Can't use this outside of guild").await?; return Ok(()); } let api = &ctx.data().radio_browser; let stations_result = match link_or_string(&name) { LinkString::Link => { if name.ends_with(".m3u") { ctx.reply_ephemeral("Sorry, currently I don't support m3u files").await?; return Ok(()); } api.get_stations().byurl(name).await }, LinkString::String => { let (name, country, language) = match parse_radio_autocomplete(&name) { Some(tuple) => tuple, None => { ctx.reply_ephemeral("Please use either direct URL or provided autocomplete").await?; return Ok(()) } }; api.get_stations() .name_exact(true) .name(name) .country_exact(true) .country(country) .language_exact(true) .language(language) .send().await } }; let Ok(stations) = stations_result else { ctx.reply_ephemeral("There was an error with radio API!").await?; return Ok(()) }; let Some(station) = stations.first() else { ctx.reply_ephemeral("Radio station not found!").await?; return Ok(()) }; let manager = songbird::get(ctx.serenity_context()) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let Some(guild_id) = ctx.guild_id() else { ctx.reply_ephemeral("Guild id not found").await?; return Ok(()) }; if manager.get(guild_id).is_none() { if let Err(e) = connect(&ctx, guild_id, channel).await { ctx.reply_ephemeral(&e.to_string()).await?; println!("SONGBIRD MANAGER ERROR: {}", e.to_string()); return Ok(()) } } if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; let client = Client::new(); let request = HttpRequest::new(client, station.url.clone()); let input = Input::from(request); handler.play_only_input(input); } else { ctx.reply_ephemeral("Not in a voice channel").await?; return Ok(()) } { let mut hash_map = ctx.data().playing_info.lock().await; hash_map.insert(guild_id, PlayingInfoType::RadioInfo(station.clone())); } ctx.reply_ephemeral("Done!").await?; Ok(()) } async fn autocomplete_radio( ctx: Context<'_>, partial: &str ) -> Vec { let api = &ctx.data().radio_browser; let stations: Vec = match api.get_stations() .limit(MAX_ENTRIES) .name(partial) .hidebroken(true) .send().await { Ok(stations) => { stations.iter().map(|station: &radiobrowser::ApiStation| format!("Name: {} Country: {} Language: {}", station.name, station.country, station.language) ).collect() }, Err(_) => return vec!["".to_owned()] }; // dbg!(&stations); return stations // return vec![]; } // TODO // autocomplete // search with buttons and list in embed with direct links to stream // embed showing currently playing song // use trace::trace; // trace::init_depth_var!(); // TODO: autocomplete radio stream URLs // #[trace] #[poise::command( slash_command, description_localized("en-US", "Search for a radio") )] pub async fn search(ctx: Context<'_>, #[description = "Radio station: "] name: String, #[description = "Country: "] country: Option, #[description = "Tag: "] tag: Option, #[description = "Number of entries on page: "] entries: Option ) -> Result<(), Error> { let api = &ctx.data().radio_browser; let entries = entries.unwrap_or(NumberOfEntries::Ten); let search_builder = api.get_stations() .limit(entries.name()) .name(name) .tag(tag.unwrap_or("".to_owned())) .country(country.unwrap_or("".to_owned())) .hidebroken(true); paginate_search_stations(&ctx, &search_builder, entries).await?; Ok(()) }