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 { pub fn thumbnail(&self) -> String {
self.audio_item self.audio_item
.covers .covers

View File

@ -31,7 +31,11 @@ pub enum SessionCommand {
GetPlayer(oneshot::Sender<PlayerHandle>), GetPlayer(oneshot::Sender<PlayerHandle>),
GetActive(oneshot::Sender<bool>), GetActive(oneshot::Sender<bool>),
CreatePlaybackEmbed(SessionHandle, CommandInteraction), CreatePlaybackEmbed(
SessionHandle,
CommandInteraction,
playback_embed::UpdateBehavior,
),
CreateLyricsEmbed(SessionHandle, CommandInteraction), CreateLyricsEmbed(SessionHandle, CommandInteraction),
Reactivate(UserId, oneshot::Sender<Result<()>>), Reactivate(UserId, oneshot::Sender<Result<()>>),
@ -207,8 +211,8 @@ impl Session {
SessionCommand::GetPlayer(sender) => _ = sender.send(self.player.clone()), SessionCommand::GetPlayer(sender) => _ = sender.send(self.player.clone()),
SessionCommand::GetActive(sender) => _ = sender.send(self.active), SessionCommand::GetActive(sender) => _ = sender.send(self.active),
SessionCommand::CreatePlaybackEmbed(handle, interaction) => { SessionCommand::CreatePlaybackEmbed(handle, interaction, behavior) => {
match PlaybackEmbed::create(self, handle, interaction).await { match PlaybackEmbed::create(self, handle, interaction, behavior).await {
Ok(Some(playback_embed)) => { Ok(Some(playback_embed)) => {
self.playback_embed = Some(playback_embed); self.playback_embed = Some(playback_embed);
} }
@ -274,8 +278,10 @@ impl Session {
PlayerEvent::TrackChanged(_) => {} PlayerEvent::TrackChanged(_) => {}
} }
let force_edit = matches!(event, PlayerEvent::TrackChanged(_));
if let Some(playback_embed) = &self.playback_embed { 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; self.playback_embed = None;
} }
} }
@ -457,11 +463,16 @@ impl SessionHandle {
/// Create a playback embed as a response to an interaction /// Create a playback embed as a response to an interaction
/// ///
/// This playback embed will automatically update when certain events happen /// 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 self.commands
.send(SessionCommand::CreatePlaybackEmbed( .send(SessionCommand::CreatePlaybackEmbed(
self.clone(), self.clone(),
interaction, interaction,
behavior,
)) ))
.await?; .await?;

View File

@ -1,11 +1,12 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use log::{error, trace}; use log::{error, trace};
use poise::ChoiceParameter;
use serenity::{ use serenity::{
all::{ all::{
ButtonStyle, CommandInteraction, ComponentInteraction, ComponentInteractionCollector, ButtonStyle, CommandInteraction, ComponentInteraction, ComponentInteractionCollector,
Context, CreateActionRow, CreateButton, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, Context, CreateActionRow, CreateButton, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter,
CreateInteractionResponse, CreateInteractionResponseFollowup, CreateInteractionResponse, CreateInteractionResponseFollowup,
CreateInteractionResponseMessage, EditMessage, Message, User, CreateInteractionResponseMessage, CreateMessage, EditMessage, Message, User,
}, },
futures::StreamExt, futures::StreamExt,
}; };
@ -18,7 +19,25 @@ use crate::{Session, SessionHandle};
#[derive(Debug)] #[derive(Debug)]
pub enum Command { 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 { pub struct PlaybackEmbed {
@ -29,6 +48,8 @@ pub struct PlaybackEmbed {
last_update: Instant, last_update: Instant,
update_in: Option<Duration>, update_in: Option<Duration>,
force_edit: bool,
update_behavior: UpdateBehavior,
rx: mpsc::Receiver<Command>, rx: mpsc::Receiver<Command>,
} }
@ -38,6 +59,7 @@ impl PlaybackEmbed {
session: &Session, session: &Session,
handle: SessionHandle, handle: SessionHandle,
interaction: CommandInteraction, interaction: CommandInteraction,
update_behavior: UpdateBehavior,
) -> Result<Option<PlaybackEmbedHandle>> { ) -> Result<Option<PlaybackEmbedHandle>> {
let ctx = session.context.clone(); let ctx = session.context.clone();
@ -69,6 +91,11 @@ impl PlaybackEmbed {
) )
.await?; .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 // Retrieve message instead of editing interaction response, as those tokens are only valid for 15 minutes
let message = interaction.get_response(&ctx).await?; let message = interaction.get_response(&ctx).await?;
@ -84,6 +111,8 @@ impl PlaybackEmbed {
message, message,
last_update: Instant::now(), last_update: Instant::now(),
update_in: None, update_in: None,
force_edit: false,
update_behavior,
rx, rx,
}; };
@ -121,7 +150,7 @@ impl PlaybackEmbed {
tokio::time::sleep(update_in).await; tokio::time::sleep(update_in).await;
} }
}, if self.update_in.is_some() => { }, if self.update_in.is_some() => {
if self.update_embed().await.is_break() { if self.update_embed(self.force_edit).await.is_break() {
break; break;
} }
} }
@ -133,15 +162,16 @@ impl PlaybackEmbed {
trace!("Received command: {command:?}"); trace!("Received command: {command:?}");
match command { match command {
Command::InvokeUpdate => { Command::InvokeUpdate(force_edit) => {
if self.last_update.elapsed() < Duration::from_secs(2) { if self.last_update.elapsed() < Duration::from_secs(2) {
if self.update_in.is_some() { if self.update_in.is_some() {
return ControlFlow::Continue(()); return ControlFlow::Continue(());
} }
self.update_in = Some(Duration::from_secs(2) - self.last_update.elapsed()); self.update_in = Some(Duration::from_secs(2) - self.last_update.elapsed());
self.force_edit = force_edit;
} else { } else {
self.update_embed().await?; self.update_embed(force_edit).await?;
} }
} }
} }
@ -216,7 +246,7 @@ impl PlaybackEmbed {
Ok((player, playback_info, owner)) 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; self.update_in = None;
let Ok(owner) = self.session.owner().await else { let Ok(owner) = self.session.owner().await else {
@ -246,6 +276,30 @@ impl PlaybackEmbed {
} }
}; };
let should_pin = !force_edit && self.update_behavior.is_pinned();
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 if let Err(why) = self
.message .message
.edit( .edit(
@ -259,7 +313,8 @@ impl PlaybackEmbed {
error!("Failed to update playback embed: {why}"); error!("Failed to update playback embed: {why}");
return ControlFlow::Break(()); return ControlFlow::Break(());
}; }
}
self.last_update = Instant::now(); self.last_update = Instant::now();
@ -267,6 +322,18 @@ impl PlaybackEmbed {
} }
async fn update_not_playing(&mut self) -> Result<()> { 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 self.message
.edit(&self.ctx, EditMessage::new().embed(not_playing_embed())) .edit(&self.ctx, EditMessage::new().embed(not_playing_embed()))
.await?; .await?;
@ -284,8 +351,8 @@ impl PlaybackEmbedHandle {
!self.tx.is_closed() !self.tx.is_closed()
} }
pub async fn invoke_update(&self) -> Result<()> { pub async fn invoke_update(&self, force_edit: bool) -> Result<()> {
self.tx.send(Command::InvokeUpdate).await?; self.tx.send(Command::InvokeUpdate(force_edit)).await?;
Ok(()) Ok(())
} }
@ -331,7 +398,13 @@ fn build_embed(playback_info: &PlaybackInfo, owner: &User) -> CreateEmbed {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .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() { if let Some(show_name) = playback_info.show_name() {

View File

@ -1,14 +1,19 @@
use anyhow::Result; use anyhow::Result;
use poise::CreateReply; use poise::CreateReply;
use serenity::all::CreateEmbed; use serenity::all::CreateEmbed;
use spoticord_session::manager::SessionQuery; use spoticord_session::{manager::SessionQuery, playback_embed::UpdateBehavior};
use spoticord_utils::discord::Colors; use spoticord_utils::discord::Colors;
use crate::bot::Context; use crate::bot::Context;
/// Show details of the current song that is being played /// Show details of the current song that is being played
#[poise::command(slash_command, guild_only)] #[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 manager = ctx.data();
let guild = ctx.guild_id().expect("poise lied to me"); let guild = ctx.guild_id().expect("poise lied to me");
@ -33,7 +38,10 @@ pub async fn playing(ctx: Context<'_>) -> Result<()> {
}; };
session session
.create_playback_embed(context.interaction.clone()) .create_playback_embed(
context.interaction.clone(),
update_behavior.unwrap_or_default(),
)
.await?; .await?;
Ok(()) Ok(())