Add update behavior settings to /playing

main
DaXcess 2024-08-19 10:50:17 +02:00
parent c5ae089930
commit c7cb26a1f8
No known key found for this signature in database
GPG Key ID: CF78CC72F0FD5EAD
4 changed files with 129 additions and 30 deletions

View File

@ -65,6 +65,13 @@ impl PlaybackInfo {
}
}
pub fn album_name(&self) -> Option<String> {
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

View File

@ -31,7 +31,11 @@ pub enum SessionCommand {
GetPlayer(oneshot::Sender<PlayerHandle>),
GetActive(oneshot::Sender<bool>),
CreatePlaybackEmbed(SessionHandle, CommandInteraction),
CreatePlaybackEmbed(
SessionHandle,
CommandInteraction,
playback_embed::UpdateBehavior,
),
CreateLyricsEmbed(SessionHandle, CommandInteraction),
Reactivate(UserId, oneshot::Sender<Result<()>>),
@ -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?;

View File

@ -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<Duration>,
force_edit: bool,
update_behavior: UpdateBehavior,
rx: mpsc::Receiver<Command>,
}
@ -38,6 +59,7 @@ impl PlaybackEmbed {
session: &Session,
handle: SessionHandle,
interaction: CommandInteraction,
update_behavior: UpdateBehavior,
) -> Result<Option<PlaybackEmbedHandle>> {
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::<Vec<_>>()
.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() {

View File

@ -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(())