2025-01-06 11:51:57 +00:00
|
|
|
use std::vec;
|
|
|
|
|
2025-01-24 20:52:13 +00:00
|
|
|
use radiobrowser::RadioBrowserAPI;
|
2025-01-06 11:51:57 +00:00
|
|
|
use reqwest::Client;
|
|
|
|
|
|
|
|
use songbird::input::Input;
|
|
|
|
use songbird::input::HttpRequest;
|
|
|
|
|
2025-01-24 20:52:13 +00:00
|
|
|
use super::super::connect;
|
|
|
|
use crate::utils::poise_context_extension::ContextExt;
|
2025-01-06 11:51:57 +00:00
|
|
|
use crate::types::{Context, Error};
|
2025-01-24 20:52:13 +00:00
|
|
|
use crate::commands::voice::voice_utils::autocomplete_channel;
|
|
|
|
|
|
|
|
use super::radio_utils::paginate_search;
|
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
2025-01-06 11:51:57 +00:00
|
|
|
|
|
|
|
// TODO: autocomplete radio stream URLs
|
|
|
|
#[poise::command(
|
|
|
|
slash_command,
|
|
|
|
description_localized("en-US", "Plays music from URL source")
|
|
|
|
)]
|
2025-01-24 20:52:13 +00:00
|
|
|
pub async fn play(ctx: Context<'_>,
|
2025-01-06 11:51:57 +00:00
|
|
|
#[autocomplete = "autocomplete_channel"]
|
|
|
|
#[description = "Voice channel name: "]
|
|
|
|
channel: Option<String>,
|
2025-01-24 20:52:13 +00:00
|
|
|
#[autocomplete = "autocomplete_radio"]
|
|
|
|
#[description = "Radio station: "]
|
|
|
|
name: String,
|
2025-01-06 11:51:57 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
|
|
|
if ctx.guild().is_none() {
|
|
|
|
ctx.reply_ephemeral("Can't use this outside of guild").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() {
|
2025-01-24 20:52:13 +00:00
|
|
|
if let Err(e) = connect(&ctx, guild_id, channel).await {
|
|
|
|
ctx.reply_ephemeral(&e.to_string()).await?;
|
|
|
|
return Ok(())
|
2025-01-06 11:51:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(handler_lock) = manager.get(guild_id) {
|
|
|
|
let mut handler = handler_lock.lock().await;
|
|
|
|
|
|
|
|
let client = Client::new();
|
2025-01-24 20:52:13 +00:00
|
|
|
let request = HttpRequest::new(client, name);
|
2025-01-06 11:51:57 +00:00
|
|
|
let input = Input::from(request);
|
|
|
|
handler.play_only_input(input);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ctx.reply_ephemeral("Not in a voice channel").await?;
|
|
|
|
return Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.reply_ephemeral("Done!").await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2025-01-24 20:52:13 +00:00
|
|
|
|
|
|
|
async fn autocomplete_radio(
|
|
|
|
ctx: Context<'_>,
|
|
|
|
partial: &str
|
|
|
|
) -> Vec<String> {
|
|
|
|
|
|
|
|
let api = {
|
|
|
|
match &ctx.data().radio_browser {
|
|
|
|
Some(v) => v.clone(),
|
|
|
|
None => {
|
|
|
|
let Ok(radio_browser) = RadioBrowserAPI::new().await else {
|
|
|
|
return vec!["There was an error with radio API!".to_owned()]
|
|
|
|
};
|
|
|
|
radio_browser
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let stations: Vec<String> = match api.get_stations()
|
|
|
|
.limit(MAX_ENTRIES)
|
|
|
|
.name(partial)
|
|
|
|
.hidebroken(true)
|
|
|
|
.send().await {
|
|
|
|
Ok(stations) => {
|
|
|
|
stations.iter().map(|station|
|
|
|
|
format!("Name: {} Language: {} Bitrate: {}", station.name, station.countrycode, station.bitrate)
|
|
|
|
).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
|
|
|
|
|
|
|
|
// TODO: autocomplete radio stream URLs
|
|
|
|
#[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<String>,
|
|
|
|
#[description = "Tag: "]
|
|
|
|
tag: Option<String>
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let api = match &ctx.data().radio_browser {
|
|
|
|
Some(v) => v.clone(),
|
|
|
|
None => {
|
|
|
|
let Ok(radio_browser) = RadioBrowserAPI::new().await else {
|
|
|
|
let _ = ctx.reply_ephemeral("There was an error with radio API!").await;
|
|
|
|
return Ok(())
|
|
|
|
};
|
|
|
|
radio_browser
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let station_search_builder = api.get_stations()
|
|
|
|
.name(name)
|
|
|
|
.tag(tag.unwrap_or("".to_owned()))
|
|
|
|
.country(country.unwrap_or("".to_owned()))
|
|
|
|
.hidebroken(true);
|
|
|
|
|
|
|
|
paginate_search(ctx, station_search_builder);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|