Updated Grid
This commit is contained in:
@@ -69,7 +69,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
||||
"<{guild_id}> Play command executed on channel {channel_id} with track: {track_url:?}"
|
||||
);
|
||||
// Handle the track url
|
||||
match enqueue_track(manager, guild_id.to_owned(), track_url).await {
|
||||
match enqueue_track(manager, guild_id.to_owned(), track_url, false).await {
|
||||
Ok(items) => {
|
||||
let mut message = format!("Added {} tracks", items.len());
|
||||
if items.is_empty() {
|
||||
@@ -103,45 +103,59 @@ pub async fn enqueue_track(
|
||||
manager: &Arc<Songbird>,
|
||||
guild_id: GuildId,
|
||||
track_url: &str,
|
||||
loop_enabled: bool,
|
||||
) -> Result<Vec<YtDlpItem>> {
|
||||
let mut playlist_items: Vec<YtDlpItem> = Vec::new();
|
||||
if let Some(handler_lock) = manager.get(guild_id) {
|
||||
let mut handler = handler_lock.lock().await;
|
||||
let guild = GuildCache::find_by_id(guild_id.get() as i64).await.unwrap();
|
||||
let valid = is_valid_url(track_url);
|
||||
// Validate URL before doing any I/O
|
||||
if !is_valid_url(track_url) {
|
||||
log::warn!("<{guild_id}> Invalid track url: {}", track_url);
|
||||
return Err(Error::new(422, format!("Invalid track url: {}", track_url)));
|
||||
}
|
||||
|
||||
// Check if the URL is valid
|
||||
if !valid {
|
||||
log::warn!("<{guild_id}> Invalid track url: {}", track_url);
|
||||
return Err(Error::new(422, format!("Invalid track url: {}", track_url)));
|
||||
}
|
||||
// Verify there is an active voice session
|
||||
if manager.get(guild_id).is_none() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
playlist_items = get_ytdlp_items(track_url)?;
|
||||
// Fetch yt-dlp metadata
|
||||
let playlist_items = get_ytdlp_items(track_url).await?;
|
||||
if playlist_items.is_empty() {
|
||||
return Ok(playlist_items);
|
||||
}
|
||||
|
||||
// Collect TrackInfo for the queue store before borrowing `item` in the loop
|
||||
let track_infos: Vec<TrackInfo> = playlist_items
|
||||
.iter()
|
||||
.map(|item| TrackInfo {
|
||||
title: item.get_title().to_owned(),
|
||||
url: item.get_url().to_owned(),
|
||||
})
|
||||
.collect();
|
||||
// Fetch guild config
|
||||
let guild = GuildCache::find_by_id(guild_id.get() as i64).await.unwrap();
|
||||
let volume = guild.volume as f32 / 100.0;
|
||||
|
||||
// Add each track to the queue
|
||||
for item in &playlist_items {
|
||||
let volume = guild.volume as f32 / 100.0;
|
||||
let http_client = get_client();
|
||||
// Store track metadata
|
||||
let track_infos: Vec<TrackInfo> = playlist_items
|
||||
.iter()
|
||||
.map(|item| TrackInfo {
|
||||
title: item.get_title().to_owned(),
|
||||
url: item.get_url().to_owned(),
|
||||
duration_secs: item.get_duration(),
|
||||
loop_enabled,
|
||||
})
|
||||
.collect();
|
||||
enqueue_tracks(guild_id.get(), track_infos);
|
||||
|
||||
// Enqueue the tracks
|
||||
let http_client = get_client();
|
||||
for item in &playlist_items {
|
||||
if let Some(handler_lock) = manager.get(guild_id) {
|
||||
let source = YoutubeDl::new(http_client.to_owned(), item.get_url().to_owned());
|
||||
let input: Input = source.into();
|
||||
let track_title = item.get_title().to_owned();
|
||||
|
||||
let mut handler = handler_lock.lock().await;
|
||||
let track_handle: TrackHandle = handler.enqueue_input(input).await;
|
||||
|
||||
// Set the volume
|
||||
let _ = track_handle.set_volume(volume);
|
||||
|
||||
log::debug!("<{guild_id}> Added track: {}", track_title);
|
||||
if let Err(err) = track_handle.set_volume(volume) {
|
||||
log::warn!("Failed to set volume for track {}: {}", track_title, err);
|
||||
};
|
||||
if loop_enabled {
|
||||
if let Err(err) = track_handle.enable_loop() {
|
||||
log::warn!("Failed to enable loop for track {}: {}", track_title, err);
|
||||
};
|
||||
}
|
||||
handler.remove_all_global_events();
|
||||
handler.add_global_event(
|
||||
Event::Track(TrackEvent::End),
|
||||
@@ -150,25 +164,23 @@ pub async fn enqueue_track(
|
||||
call: manager.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Store track metadata so the REST API can expose queue info
|
||||
enqueue_tracks(guild_id.get(), track_infos);
|
||||
|
||||
if handler.queue().is_empty() {
|
||||
let _ = handler.queue().resume();
|
||||
// Release the lock
|
||||
drop(handler);
|
||||
log::debug!("<{guild_id}> Added track: {}", track_title);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(playlist_items)
|
||||
}
|
||||
|
||||
pub fn get_ytdlp_items(url: &str) -> Result<Vec<YtDlpItem>> {
|
||||
pub async fn get_ytdlp_items(url: &str) -> Result<Vec<YtDlpItem>> {
|
||||
let output = YtDlp::new()
|
||||
.arg("--flat-playlist")
|
||||
.arg("--dump-json")
|
||||
.arg("--no-check-formats")
|
||||
.arg(url)
|
||||
.execute()?;
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
// Check if yt-dlp exited successfully; log stderr if not
|
||||
if !output.status.success() {
|
||||
|
||||
@@ -13,6 +13,10 @@ use std::{
|
||||
pub struct TrackInfo {
|
||||
pub title: String,
|
||||
pub url: String,
|
||||
/// Total duration in seconds, if known (from yt-dlp metadata).
|
||||
pub duration_secs: Option<f64>,
|
||||
/// Whether this track should loop indefinitely.
|
||||
pub loop_enabled: bool,
|
||||
}
|
||||
|
||||
/// Global map of guild_id → ordered queue of TrackInfo.
|
||||
@@ -21,9 +25,7 @@ static TRACK_QUEUES: OnceLock<Arc<DashMap<u64, VecDeque<TrackInfo>>>> = OnceLock
|
||||
|
||||
/// Call once from the `ready` event handler to initialise the store.
|
||||
pub fn init_track_queues() {
|
||||
TRACK_QUEUES
|
||||
.set(Arc::new(DashMap::new()))
|
||||
.ok();
|
||||
TRACK_QUEUES.set(Arc::new(DashMap::new())).ok();
|
||||
}
|
||||
|
||||
/// Returns a reference to the global TRACK_QUEUES map.
|
||||
@@ -61,9 +63,7 @@ pub fn clear_queue(guild_id: u64) {
|
||||
pub fn get_queue(guild_id: u64) -> Vec<TrackInfo> {
|
||||
queues()
|
||||
.get(&guild_id)
|
||||
.map(|q: dashmap::mapref::one::Ref<u64, VecDeque<TrackInfo>>| {
|
||||
q.iter().cloned().collect()
|
||||
})
|
||||
.map(|q: dashmap::mapref::one::Ref<u64, VecDeque<TrackInfo>>| q.iter().cloned().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -86,3 +86,61 @@ pub async fn get_is_paused(guild_id: u64) -> bool {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Toggle or set loop on the currently playing track
|
||||
pub async fn set_loop_current(guild_id: u64, enabled: bool) -> bool {
|
||||
// Update our metadata store first
|
||||
let updated = {
|
||||
if let Some(mut q) = queues().get_mut(&guild_id) {
|
||||
if let Some(front) = q.front_mut() {
|
||||
front.loop_enabled = enabled;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if !updated {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tell songbird to loop / unloop the live track handle
|
||||
let manager = get_songbird();
|
||||
let serenity_guild_id = GuildId::from(guild_id);
|
||||
if let Some(handler_lock) = manager.get(serenity_guild_id) {
|
||||
let handler = handler_lock.lock().await;
|
||||
let current = handler.queue().current();
|
||||
drop(handler);
|
||||
if let Some(track) = current {
|
||||
if enabled {
|
||||
let _ = track.enable_loop();
|
||||
} else {
|
||||
let _ = track.disable_loop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the current playback position (in seconds) for the active track in the
|
||||
/// given guild, or `0.0` if nothing is playing or the info is unavailable.
|
||||
pub async fn get_current_position(guild_id: u64) -> f64 {
|
||||
let manager = get_songbird();
|
||||
let serenity_guild_id = GuildId::from(guild_id);
|
||||
if let Some(handler_lock) = manager.get(serenity_guild_id) {
|
||||
let handler = handler_lock.lock().await;
|
||||
let current = handler.queue().current();
|
||||
drop(handler);
|
||||
if let Some(track) = current {
|
||||
return track
|
||||
.get_info()
|
||||
.await
|
||||
.map(|info| info.position.as_secs_f64())
|
||||
.unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
@@ -44,12 +44,10 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) {
|
||||
pub async fn skip_track(manager: &Arc<Songbird>, guild_id: &GuildId) -> Result<(), String> {
|
||||
if let Some(handler_lock) = manager.get(guild_id.to_owned()) {
|
||||
let handler = handler_lock.lock().await;
|
||||
handler
|
||||
.queue()
|
||||
.skip()
|
||||
.map_err(|e| e.to_string())?;
|
||||
handler.queue().skip().map_err(|e| e.to_string())?;
|
||||
// Pop the current track from our metadata store; the next track (if any) moves to front
|
||||
pop_front(guild_id.get());
|
||||
drop(handler);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No active audio session in this guild".to_string())
|
||||
|
||||
Reference in New Issue
Block a user