Add update behavior settings to /playing
parent
c5ae089930
commit
c7cb26a1f8
|
@ -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
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
||||||
|
|
|
@ -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,20 +276,45 @@ impl PlaybackEmbed {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(why) = self
|
let should_pin = !force_edit && self.update_behavior.is_pinned();
|
||||||
.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(());
|
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();
|
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() {
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
Loading…
Reference in New Issue