173 lines
5 KiB
Rust
173 lines
5 KiB
Rust
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<String>,
|
|
#[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<String> {
|
|
|
|
let api = &ctx.data().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: &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<String>,
|
|
#[description = "Tag: "]
|
|
tag: Option<String>,
|
|
#[description = "Number of entries on page: "]
|
|
entries: Option<NumberOfEntries>
|
|
) -> 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(())
|
|
}
|