From c7cb26a1f8377ca0622a0e8861d494abda39dc15 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 19 Aug 2024 10:50:17 +0200 Subject: [PATCH] Add update behavior settings to /playing --- spoticord_player/src/info.rs | 7 ++ spoticord_session/src/lib.rs | 21 ++++- spoticord_session/src/playback_embed.rs | 117 +++++++++++++++++++----- src/commands/music/playing.rs | 14 ++- 4 files changed, 129 insertions(+), 30 deletions(-) diff --git a/spoticord_player/src/info.rs b/spoticord_player/src/info.rs index 62bf692..32facec 100644 --- a/spoticord_player/src/info.rs +++ b/spoticord_player/src/info.rs @@ -65,6 +65,13 @@ impl PlaybackInfo { } } + pub fn album_name(&self) -> Option { + match &self.audio_item.unique_fields { + UniqueFields::Episode { .. } => None, + UniqueFields::Track { album, .. } => Some(album.to_string()), + } + } + pub fn thumbnail(&self) -> String { self.audio_item .covers diff --git a/spoticord_session/src/lib.rs b/spoticord_session/src/lib.rs index f77c6f3..7b86285 100644 --- a/spoticord_session/src/lib.rs +++ b/spoticord_session/src/lib.rs @@ -31,7 +31,11 @@ pub enum SessionCommand { GetPlayer(oneshot::Sender), GetActive(oneshot::Sender), - CreatePlaybackEmbed(SessionHandle, CommandInteraction), + CreatePlaybackEmbed( + SessionHandle, + CommandInteraction, + playback_embed::UpdateBehavior, + ), CreateLyricsEmbed(SessionHandle, CommandInteraction), Reactivate(UserId, oneshot::Sender>), @@ -207,8 +211,8 @@ impl Session { SessionCommand::GetPlayer(sender) => _ = sender.send(self.player.clone()), SessionCommand::GetActive(sender) => _ = sender.send(self.active), - SessionCommand::CreatePlaybackEmbed(handle, interaction) => { - match PlaybackEmbed::create(self, handle, interaction).await { + SessionCommand::CreatePlaybackEmbed(handle, interaction, behavior) => { + match PlaybackEmbed::create(self, handle, interaction, behavior).await { Ok(Some(playback_embed)) => { self.playback_embed = Some(playback_embed); } @@ -274,8 +278,10 @@ impl Session { PlayerEvent::TrackChanged(_) => {} } + let force_edit = matches!(event, PlayerEvent::TrackChanged(_)); + if let Some(playback_embed) = &self.playback_embed { - if playback_embed.invoke_update().await.is_err() { + if playback_embed.invoke_update(force_edit).await.is_err() { self.playback_embed = None; } } @@ -457,11 +463,16 @@ impl SessionHandle { /// Create a playback embed as a response to an interaction /// /// This playback embed will automatically update when certain events happen - pub async fn create_playback_embed(&self, interaction: CommandInteraction) -> Result<()> { + pub async fn create_playback_embed( + &self, + interaction: CommandInteraction, + behavior: playback_embed::UpdateBehavior, + ) -> Result<()> { self.commands .send(SessionCommand::CreatePlaybackEmbed( self.clone(), interaction, + behavior, )) .await?; diff --git a/spoticord_session/src/playback_embed.rs b/spoticord_session/src/playback_embed.rs index 82bd0d5..2070d03 100644 --- a/spoticord_session/src/playback_embed.rs +++ b/spoticord_session/src/playback_embed.rs @@ -1,11 +1,12 @@ use anyhow::{anyhow, Result}; use log::{error, trace}; +use poise::ChoiceParameter; use serenity::{ all::{ ButtonStyle, CommandInteraction, ComponentInteraction, ComponentInteractionCollector, Context, CreateActionRow, CreateButton, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseFollowup, - CreateInteractionResponseMessage, EditMessage, Message, User, + CreateInteractionResponseMessage, CreateMessage, EditMessage, Message, User, }, futures::StreamExt, }; @@ -18,7 +19,25 @@ use crate::{Session, SessionHandle}; #[derive(Debug)] pub enum Command { - InvokeUpdate, + InvokeUpdate(bool), +} + +#[derive(Debug, Default, ChoiceParameter)] +pub enum UpdateBehavior { + #[default] + Default, + Static, + Pinned, +} + +impl UpdateBehavior { + pub fn is_static(&self) -> bool { + matches!(self, Self::Static) + } + + pub fn is_pinned(&self) -> bool { + matches!(self, Self::Pinned) + } } pub struct PlaybackEmbed { @@ -29,6 +48,8 @@ pub struct PlaybackEmbed { last_update: Instant, update_in: Option, + force_edit: bool, + update_behavior: UpdateBehavior, rx: mpsc::Receiver, } @@ -38,6 +59,7 @@ impl PlaybackEmbed { session: &Session, handle: SessionHandle, interaction: CommandInteraction, + update_behavior: UpdateBehavior, ) -> Result> { let ctx = session.context.clone(); @@ -69,6 +91,11 @@ impl PlaybackEmbed { ) .await?; + // If this is a static embed, we don't need to return any handles + if update_behavior.is_static() { + return Ok(None); + } + // Retrieve message instead of editing interaction response, as those tokens are only valid for 15 minutes let message = interaction.get_response(&ctx).await?; @@ -84,6 +111,8 @@ impl PlaybackEmbed { message, last_update: Instant::now(), update_in: None, + force_edit: false, + update_behavior, rx, }; @@ -121,7 +150,7 @@ impl PlaybackEmbed { tokio::time::sleep(update_in).await; } }, if self.update_in.is_some() => { - if self.update_embed().await.is_break() { + if self.update_embed(self.force_edit).await.is_break() { break; } } @@ -133,15 +162,16 @@ impl PlaybackEmbed { trace!("Received command: {command:?}"); match command { - Command::InvokeUpdate => { + Command::InvokeUpdate(force_edit) => { if self.last_update.elapsed() < Duration::from_secs(2) { if self.update_in.is_some() { return ControlFlow::Continue(()); } self.update_in = Some(Duration::from_secs(2) - self.last_update.elapsed()); + self.force_edit = force_edit; } else { - self.update_embed().await?; + self.update_embed(force_edit).await?; } } } @@ -216,7 +246,7 @@ impl PlaybackEmbed { Ok((player, playback_info, owner)) } - async fn update_embed(&mut self) -> ControlFlow<(), ()> { + async fn update_embed(&mut self, force_edit: bool) -> ControlFlow<(), ()> { self.update_in = None; let Ok(owner) = self.session.owner().await else { @@ -246,20 +276,45 @@ impl PlaybackEmbed { } }; - if let Err(why) = self - .message - .edit( - &self.ctx, - EditMessage::new() - .embed(build_embed(&playback_info, &owner)) - .components(vec![build_buttons(self.id, playback_info.playing())]), - ) - .await - { - error!("Failed to update playback embed: {why}"); + let should_pin = !force_edit && self.update_behavior.is_pinned(); - return ControlFlow::Break(()); - }; + if should_pin { + self.message.delete(&self.ctx).await.ok(); + + match self + .message + .channel_id + .send_message( + &self.ctx, + CreateMessage::new() + .embed(build_embed(&playback_info, &owner)) + .components(vec![build_buttons(self.id, playback_info.playing())]), + ) + .await + { + Ok(message) => self.message = message, + Err(why) => { + error!("Failed to update playback embed: {why}"); + + return ControlFlow::Break(()); + } + }; + } else { + if let Err(why) = self + .message + .edit( + &self.ctx, + EditMessage::new() + .embed(build_embed(&playback_info, &owner)) + .components(vec![build_buttons(self.id, playback_info.playing())]), + ) + .await + { + error!("Failed to update playback embed: {why}"); + + return ControlFlow::Break(()); + } + } self.last_update = Instant::now(); @@ -267,6 +322,18 @@ impl PlaybackEmbed { } async fn update_not_playing(&mut self) -> Result<()> { + // If pinned, try to delete old message and send new one + if self.update_behavior.is_pinned() { + self.message.delete(&self.ctx).await.ok(); + self.message = self + .message + .channel_id + .send_message(&self.ctx, CreateMessage::new().embed(not_playing_embed())) + .await?; + + return Ok(()); + } + self.message .edit(&self.ctx, EditMessage::new().embed(not_playing_embed())) .await?; @@ -284,8 +351,8 @@ impl PlaybackEmbedHandle { !self.tx.is_closed() } - pub async fn invoke_update(&self) -> Result<()> { - self.tx.send(Command::InvokeUpdate).await?; + pub async fn invoke_update(&self, force_edit: bool) -> Result<()> { + self.tx.send(Command::InvokeUpdate(force_edit)).await?; Ok(()) } @@ -331,7 +398,13 @@ fn build_embed(playback_info: &PlaybackInfo, owner: &User) -> CreateEmbed { .collect::>() .join(", "); - description += &format!("By {artists}\n\n"); + description += &format!("By {artists}\n"); + } + + if let Some(album_name) = playback_info.album_name() { + description += &format!("In album: **{album_name}**\n\n"); + } else { + description += "\n"; } if let Some(show_name) = playback_info.show_name() { diff --git a/src/commands/music/playing.rs b/src/commands/music/playing.rs index 45f31d8..2ce81b1 100644 --- a/src/commands/music/playing.rs +++ b/src/commands/music/playing.rs @@ -1,14 +1,19 @@ use anyhow::Result; use poise::CreateReply; use serenity::all::CreateEmbed; -use spoticord_session::manager::SessionQuery; +use spoticord_session::{manager::SessionQuery, playback_embed::UpdateBehavior}; use spoticord_utils::discord::Colors; use crate::bot::Context; /// Show details of the current song that is being played #[poise::command(slash_command, guild_only)] -pub async fn playing(ctx: Context<'_>) -> Result<()> { +pub async fn playing( + ctx: Context<'_>, + #[description = "How Spoticord should update this information"] update_behavior: Option< + UpdateBehavior, + >, +) -> Result<()> { let manager = ctx.data(); let guild = ctx.guild_id().expect("poise lied to me"); @@ -33,7 +38,10 @@ pub async fn playing(ctx: Context<'_>) -> Result<()> { }; session - .create_playback_embed(context.interaction.clone()) + .create_playback_embed( + context.interaction.clone(), + update_behavior.unwrap_or_default(), + ) .await?; Ok(())