Simplified enqueue_track to parse track type (playlist or single track)
This commit is contained in:
@@ -56,14 +56,8 @@ pub async fn leave_voice_channel(manager: &Arc<Songbird>, guild_id: &GuildId) ->
|
|||||||
* 1st tuple value is if the URL is valid.
|
* 1st tuple value is if the URL is valid.
|
||||||
* 2nd tuple value is if the URL is a playlist.
|
* 2nd tuple value is if the URL is a playlist.
|
||||||
*/
|
*/
|
||||||
fn is_valid_url(url: &str) -> (bool, bool) {
|
fn is_valid_url(url: &str) -> bool {
|
||||||
Url::parse(url).ok().map_or((false, false), |valid_url| {
|
Url::parse(url).ok().map_or(false, |valid_url| true)
|
||||||
let is_playlist: bool = valid_url
|
|
||||||
.query_pairs()
|
|
||||||
.find(|(key, _)| key == "list")
|
|
||||||
.map_or(false, |_| true);
|
|
||||||
(true, is_playlist)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use songbird::{Event, EventHandler, Songbird, TrackEvent};
|
|||||||
|
|
||||||
use crate::bot::commands::audio::leave_voice_channel;
|
use crate::bot::commands::audio::leave_voice_channel;
|
||||||
use crate::data::guilds::GuildCache;
|
use crate::data::guilds::GuildCache;
|
||||||
use crate::bot::ytdlp::{PlaylistItem, YtDlp};
|
use crate::bot::ytdlp::{YtDlp, YtDlpItem};
|
||||||
use crate::error::{SirenResult, Error as SirenError};
|
use crate::error::{SirenResult, Error as SirenError};
|
||||||
use crate::HttpKey;
|
use crate::HttpKey;
|
||||||
|
|
||||||
@@ -57,12 +57,12 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
|||||||
log::debug!("<{guild_id}> Play command executed on {channel_id} with track: {track_url:?}");
|
log::debug!("<{guild_id}> Play command executed on {channel_id} with track: {track_url:?}");
|
||||||
// Handle the track url
|
// Handle the track url
|
||||||
match enqueue_track(ctx, manager, guild_id.to_owned(), track_url).await {
|
match enqueue_track(ctx, manager, guild_id.to_owned(), track_url).await {
|
||||||
Ok(count) => {
|
Ok(items) => {
|
||||||
let mut message = format!("Playing {} tracks", count);
|
let mut message = format!("Added {} tracks", items.len());
|
||||||
if count == 0 {
|
if items.len() == 0 {
|
||||||
message = "No tracks were played".to_string();
|
message = "No tracks were played".to_string();
|
||||||
} else if count == 1 {
|
} else if items.len() == 1 {
|
||||||
message = "Playing 1 track".to_string();
|
message = format!("Added **{}**", items[0].get_title());
|
||||||
}
|
}
|
||||||
edit_response(&ctx, &command, message).await;
|
edit_response(&ctx, &command, message).await;
|
||||||
}
|
}
|
||||||
@@ -84,42 +84,32 @@ pub async fn enqueue_track(
|
|||||||
manager: Arc<Songbird>,
|
manager: Arc<Songbird>,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
track_url: &str,
|
track_url: &str,
|
||||||
) -> SirenResult<i32> {
|
) -> SirenResult<Vec<YtDlpItem>> {
|
||||||
let mut track_count = 0;
|
let mut playlist_items: Vec<YtDlpItem> = Vec::new();
|
||||||
if let Some(handler_lock) = manager.get(guild_id) {
|
if let Some(handler_lock) = manager.get(guild_id) {
|
||||||
let mut handler = handler_lock.lock().await;
|
let mut handler = handler_lock.lock().await;
|
||||||
let guild = GuildCache::get_by_id(guild_id.get() as i64).await?.unwrap();
|
let guild = GuildCache::get_by_id(guild_id.get() as i64).await?.unwrap();
|
||||||
let valid = is_valid_url(&track_url);
|
let valid = is_valid_url(&track_url);
|
||||||
|
|
||||||
// Check if the URL is valid
|
// Check if the URL is valid
|
||||||
if !valid.0 {
|
if !valid {
|
||||||
log::warn!("<{guild_id}> Invalid track url: {}", track_url);
|
log::warn!("<{guild_id}> Invalid track url: {}", track_url);
|
||||||
return Err(SirenError::new(
|
return Err(SirenError::new(
|
||||||
422,
|
422,
|
||||||
format!("Invalid track url: {}", track_url),
|
format!("Invalid track url: {}", track_url),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut playlist_items: Vec<PlaylistItem> = Vec::new();
|
|
||||||
// Check if the URL is a playlist or a single track
|
playlist_items = match get_ytdlp_items(&track_url) {
|
||||||
if valid.1 {
|
|
||||||
playlist_items = match get_playlist_urls(&track_url) {
|
|
||||||
Ok(items) => items,
|
Ok(items) => items,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("<{guild_id}> Failed to get playlist urls: {}", err);
|
log::warn!("<{guild_id}> Failed to get playlist urls: {}", err);
|
||||||
return Err(SirenError::new(422, err.to_string()));
|
return Err(SirenError::new(422, err.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
let playlist_item = PlaylistItem {
|
|
||||||
id: "".to_string(),
|
|
||||||
url: track_url.to_string(),
|
|
||||||
title: "".to_string(),
|
|
||||||
duration: 0,
|
|
||||||
playlist_index: 0,
|
|
||||||
};
|
|
||||||
playlist_items.push(playlist_item);
|
|
||||||
}
|
|
||||||
// Add each track to the queue
|
// Add each track to the queue
|
||||||
for item in playlist_items {
|
for item in &playlist_items {
|
||||||
let volume = guild.volume as f32 / 100.0;
|
let volume = guild.volume as f32 / 100.0;
|
||||||
let http_client = {
|
let http_client = {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
@@ -128,21 +118,17 @@ pub async fn enqueue_track(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.expect("Guaranteed to exist in the typemap.")
|
.expect("Guaranteed to exist in the typemap.")
|
||||||
};
|
};
|
||||||
let source = YoutubeDl::new(http_client, item.url.to_owned());
|
|
||||||
let mut input: Input = source.into();
|
let source = YoutubeDl::new(http_client, item.get_url().to_owned());
|
||||||
let metadata = match input.aux_metadata().await {
|
let input: Input = source.into();
|
||||||
Ok(metadata) => metadata,
|
let track_title = item.get_title().to_owned();
|
||||||
Err(err) => {
|
|
||||||
log::warn!("<{guild_id}> Failed to get metadata for track: {err}");
|
|
||||||
let _ = leave_voice_channel(&manager, &guild_id).await;
|
|
||||||
return Err(SirenError::new(422, err.to_string()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let track_handle: TrackHandle;
|
let track_handle: TrackHandle;
|
||||||
track_handle = handler.enqueue_input(input).await;
|
track_handle = handler.enqueue_input(input).await;
|
||||||
|
|
||||||
// Set the volume
|
// Set the volume
|
||||||
let _ = track_handle.set_volume(volume);
|
let _ = track_handle.set_volume(volume);
|
||||||
let track_title = metadata.title.unwrap();
|
|
||||||
log::debug!("<{guild_id}> Added track: {}", track_title);
|
log::debug!("<{guild_id}> Added track: {}", track_title);
|
||||||
handler.remove_all_global_events();
|
handler.remove_all_global_events();
|
||||||
handler.add_global_event(
|
handler.add_global_event(
|
||||||
@@ -152,29 +138,28 @@ pub async fn enqueue_track(
|
|||||||
call: manager.clone(),
|
call: manager.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
track_count += 1;
|
|
||||||
}
|
}
|
||||||
if handler.queue().is_empty() {
|
if handler.queue().is_empty() {
|
||||||
let _ = handler.queue().resume();
|
let _ = handler.queue().resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(track_count)
|
Ok(playlist_items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_playlist_urls(url: &str) -> SirenResult<Vec<PlaylistItem>> {
|
pub fn get_ytdlp_items(url: &str) -> SirenResult<Vec<YtDlpItem>> {
|
||||||
let output = YtDlp::new()
|
let output = YtDlp::new()
|
||||||
.arg("--flat-playlist")
|
.arg("--flat-playlist")
|
||||||
.arg("--dump-json")
|
.arg("--dump-json")
|
||||||
.arg(url)
|
.arg(url)
|
||||||
.execute()?;
|
.execute()?;
|
||||||
let items: Vec<PlaylistItem> = String::from_utf8(output.stdout)?
|
let items: Vec<YtDlpItem> = String::from_utf8(output.stdout)?
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Some(
|
||||||
serde_json::from_slice::<PlaylistItem>(line.as_bytes())
|
serde_json::from_slice::<YtDlpItem>(line.as_bytes())
|
||||||
.map_err(|err| SirenError::new(500, err.to_string())),
|
.map_err(|err| SirenError::new(500, err.to_string())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,38 @@
|
|||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, Mentionable, UserId
|
CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, Mentionable,
|
||||||
|
UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::bot::commands::{create_response, edit_response, user_id_dm};
|
use crate::bot::commands::{create_response, edit_response, user_id_dm};
|
||||||
|
|
||||||
pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
||||||
// Check if the roll result is private
|
// Check if the roll result is private
|
||||||
let private = command.data.options.iter().find(|opt| opt.name == "private")
|
let private = command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == "private")
|
||||||
.and_then(|o| o.value.as_bool())
|
.and_then(|o| o.value.as_bool())
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
|
||||||
// Retrieve the DM's name or ID from the options (optional)
|
// Retrieve the DM's name or ID from the options (optional)
|
||||||
let user = command.data.options.iter().find(|opt| opt.name == "user")
|
let user = command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == "user")
|
||||||
.and_then(|o| o.value.as_mentionable());
|
.and_then(|o| o.value.as_mentionable());
|
||||||
|
|
||||||
create_response(&ctx, &command, format!("Rolling..."), private).await;
|
create_response(&ctx, &command, format!("Rolling..."), private).await;
|
||||||
|
|
||||||
let dice_string = match command.data.options.get(0)
|
let dice_string = match command
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.get(0)
|
||||||
.and_then(|o| o.value.as_str())
|
.and_then(|o| o.value.as_str())
|
||||||
.map(|s| s.split_whitespace().collect::<String>()) {
|
.map(|s| s.split_whitespace().collect::<String>())
|
||||||
|
{
|
||||||
Some(dice_value) => dice_value,
|
Some(dice_value) => dice_value,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("Missing or invalid dice option");
|
log::warn!("Missing or invalid dice option");
|
||||||
@@ -55,10 +68,20 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
|||||||
match user {
|
match user {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let user_id = UserId::new(id.get());
|
let user_id = UserId::new(id.get());
|
||||||
user_id_dm(&ctx, &user_id, format!("Dice roll from {}: {}", &command.user.mention(), response)).await;
|
user_id_dm(
|
||||||
edit_response(&ctx, command, format!("Sending dice roll results to {}", &user_id.mention())).await;
|
&ctx,
|
||||||
},
|
&user_id,
|
||||||
None => edit_response(&ctx, &command, response).await
|
format!("Dice roll from {}: {}", &command.user.mention(), response),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
edit_response(
|
||||||
|
&ctx,
|
||||||
|
command,
|
||||||
|
format!("Sending dice roll results to {}", &user_id.mention()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
None => edit_response(&ctx, &command, response).await,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
@@ -94,7 +117,9 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Parse the number of sides
|
// Parse the number of sides
|
||||||
let sides_part = parts.next().ok_or_else(|| format!("Invalid dice string: {}", dice))?;
|
let sides_part = parts
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("Invalid dice string: {}", dice))?;
|
||||||
let sides = match sides_part.parse::<u32>() {
|
let sides = match sides_part.parse::<u32>() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if [4, 6, 8, 10, 12, 20, 100].contains(&n) {
|
if [4, 6, 8, 10, 12, 20, 100].contains(&n) {
|
||||||
@@ -106,10 +131,12 @@ fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => return Err(format!(
|
Err(_) => {
|
||||||
|
return Err(format!(
|
||||||
"Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}",
|
"Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}",
|
||||||
sides_part
|
sides_part
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine if there's a modifier (+ or -)
|
// Determine if there's a modifier (+ or -)
|
||||||
@@ -152,7 +179,11 @@ pub fn register() -> CreateCommand {
|
|||||||
.required(false),
|
.required(false),
|
||||||
)
|
)
|
||||||
.add_option(
|
.add_option(
|
||||||
CreateCommandOption::new(CommandOptionType::Mentionable, "user", "User to receive the roll results")
|
CreateCommandOption::new(
|
||||||
|
CommandOptionType::Mentionable,
|
||||||
|
"user",
|
||||||
|
"User to receive the roll results",
|
||||||
|
)
|
||||||
.required(false),
|
.required(false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
CommandInteraction, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, EditInteractionResponse, InteractionResponseFlags, Message, User, UserId
|
CommandInteraction, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage,
|
||||||
|
EditInteractionResponse, InteractionResponseFlags, Message, User, UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
@@ -13,11 +14,7 @@ pub async fn process_message(ctx: &Context, command: &CommandInteraction, privat
|
|||||||
create_response(&ctx, &command, format!("Processing..."), private).await;
|
create_response(&ctx, &command, format!("Processing..."), private).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_id_dm(
|
pub async fn user_id_dm(ctx: &Context, user_id: &UserId, content: String) -> Option<Message> {
|
||||||
ctx: &Context,
|
|
||||||
user_id: &UserId,
|
|
||||||
content: String,
|
|
||||||
) -> Option<Message> {
|
|
||||||
let data = CreateMessage::new().content(content.to_owned());
|
let data = CreateMessage::new().content(content.to_owned());
|
||||||
return match user_id.dm(ctx, data).await {
|
return match user_id.dm(ctx, data).await {
|
||||||
Ok(message) => Some(message),
|
Ok(message) => Some(message),
|
||||||
@@ -28,11 +25,7 @@ pub async fn user_id_dm(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_dm(
|
pub async fn user_dm(ctx: &Context, user: &User, content: String) -> Option<Message> {
|
||||||
ctx: &Context,
|
|
||||||
user: &User,
|
|
||||||
content: String,
|
|
||||||
) -> Option<Message> {
|
|
||||||
let data = CreateMessage::new().content(content.to_owned());
|
let data = CreateMessage::new().content(content.to_owned());
|
||||||
return match user.direct_message(ctx, data).await {
|
return match user.direct_message(ctx, data).await {
|
||||||
Ok(message) => Some(message),
|
Ok(message) => Some(message),
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct PlaylistItem {
|
#[serde(untagged)]
|
||||||
pub id: String,
|
pub enum YtDlpItem {
|
||||||
pub url: String,
|
PlaylistItem {
|
||||||
pub title: String,
|
id: String,
|
||||||
pub duration: i32,
|
url: String,
|
||||||
pub playlist_index: i32,
|
title: String,
|
||||||
|
duration: i32,
|
||||||
|
playlist_index: i32,
|
||||||
|
},
|
||||||
|
VideoItem {
|
||||||
|
id: String,
|
||||||
|
webpage_url: String,
|
||||||
|
title: String,
|
||||||
|
duration: i32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YtDlpItem {
|
||||||
|
pub fn get_title(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
YtDlpItem::PlaylistItem { title, .. } => title,
|
||||||
|
YtDlpItem::VideoItem { title, .. } => title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_url(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
YtDlpItem::PlaylistItem { url, .. } => url,
|
||||||
|
YtDlpItem::VideoItem { webpage_url, .. } => webpage_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user