use std::vec; use poise::ChoiceParameter; use reqwest::Client; use songbird::input::Input; use songbird::input::HttpRequest; use songbird::TrackEvent; use super::super::connect; use super::link_or_string; use super::LinkString; use super::parse_radio_autocomplete; use super::radio_utils::paginate_search_stations; use super::super::voice_utils::MAX_ENTRIES; use crate::commands::voice_types::{NumberOfEntries, PlayingInfoType}; use crate::types::{Context, Error, ContextExt}; use crate::commands::voice::voice_utils::autocomplete_channel; /// Plays online radio stream #[poise::command( slash_command, description_localized("en-US", "Plays music from URL source"), subcommands("search", "play"), guild_only )] pub async fn radio(_ctx: Context<'_>) -> Result<(), Error> { Ok(()) } /// Play online radio stream directly from URL or autocompleted string #[poise::command( slash_command, description_localized("en-US", "Plays music from URL source"), category = "Voice" )] 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> { 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 events = vec![TrackEvent::End, TrackEvent::Error]; let (manager, guild_id) = match connect(&ctx, channel, events).await { Ok(result) => result, Err(e) => { 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()] }; return stations } /// Search online radios (you can use stream URL from output for /play) #[poise::command( slash_command, description_localized("en-US", "Search for a radio"), category = "Voice" )] 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(()) }