Add controls to now playing message and fix sticky menu

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/9/head
trivernis 3 years ago
parent 071e5f52fd
commit 411dd83240
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -12,4 +12,7 @@ pub enum SerenityUtilsError {
#[error("Serenity Utils not fully initialized")]
Uninitialized,
#[error("{0}")]
Msg(String),
}

@ -116,6 +116,21 @@ impl Menu<'_> {
&serde_json::to_value(current_page.0).unwrap(),
)
.await?;
let mut controls = self
.controls
.clone()
.into_iter()
.collect::<Vec<(String, ActionContainer)>>();
controls.sort_by_key(|(_, a)| a.position);
for emoji in controls.into_iter().map(|(e, _)| e) {
http.create_reaction(
message.channel_id.0,
message.id.0,
&ReactionType::Unicode(emoji.clone()),
)
.await?;
}
log::trace!("New message is {:?}", message);
handle.message_id = message.id.0;

@ -25,7 +25,8 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult {
if let Some(current) = queue_lock.current() {
let metadata = current.metadata().clone();
log::trace!("Metadata is {:?}", metadata);
let np_msg = create_now_playing_msg(ctx, msg.channel_id, &metadata).await?;
let np_msg =
create_now_playing_msg(ctx, msg.channel_id, &metadata, queue_lock.paused()).await?;
if let Some(old_np) = mem::replace(&mut queue_lock.now_playing_msg, Some(np_msg)) {
let old_np = old_np.read().await;

@ -1,19 +1,24 @@
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_channel_for_author, join_channel};
use serenity::model::id::ChannelId;
#[command]
#[only_in(guilds)]
#[description("Joins a voice channel")]
#[usage("")]
#[bucket("general")]
async fn join(ctx: &Context, msg: &Message) -> CommandResult {
async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let channel_id = get_channel_for_author(&msg.author.id, &guild)?;
let channel_id = if let Ok(arg) = args.single::<u64>() {
ChannelId(arg)
} else {
get_channel_for_author(&msg.author.id, &guild)?
};
log::debug!("Joining channel {} for guild {}", channel_id, guild.id);
join_channel(ctx, channel_id, guild.id).await;
handle_autodelete(ctx, msg).await?;

@ -192,7 +192,7 @@ fn get_channel_for_author(author_id: &UserId, guild: &Guild) -> BotResult<Channe
}
/// Returns the voice manager from the context
async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> {
pub async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> {
songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
@ -252,7 +252,7 @@ async fn play_next_in_queue(
log::trace!("Track is {:?}", track);
if let Some(np) = &queue_lock.now_playing_msg {
if let Err(e) = update_now_playing_msg(http, np, track.metadata()).await {
if let Err(e) = update_now_playing_msg(http, np, track.metadata(), false).await {
log::error!("Failed to update now playing message: {:?}", e);
}
}
@ -381,7 +381,7 @@ async fn added_multiple_msg(ctx: &Context, msg: &Message, songs: &mut Vec<Song>)
/// Returns if the given user is a dj in the given guild based on the
/// setting for the name of the dj role
async fn is_dj(ctx: &Context, guild: GuildId, user: &User) -> BotResult<bool> {
pub async fn is_dj(ctx: &Context, guild: GuildId, user: &User) -> BotResult<bool> {
let dj_role = get_setting::<String>(ctx, guild, Setting::MusicDjRole).await?;
if let Some(role_name) = dj_role {

@ -5,6 +5,7 @@ use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use crate::messages::music::update_now_playing_msg;
#[command]
#[only_in(guilds)]
@ -27,9 +28,17 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
if queue_lock.paused() {
log::debug!("Paused");
msg.channel_id.say(ctx, "Paused playback").await?;
if let (Some(menu), Some(current)) = (&queue_lock.now_playing_msg, queue_lock.current())
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
}
} else {
log::debug!("Resumed");
msg.channel_id.say(ctx, "Resumed playback").await?;
if let (Some(menu), Some(current)) = (&queue_lock.now_playing_msg, queue_lock.current())
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
}
}
} else {
msg.channel_id.say(ctx, "Nothing to pause").await?;

@ -14,7 +14,7 @@ use crate::commands::music::{
#[description("Puts a song as the next to play in the queue")]
#[usage("<song-url>")]
#[min_args(1)]
#[aliases("pn", "play-next")]
#[aliases("pn", "play-next", "playnext")]
#[bucket("music_api")]
async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query = args.message();

@ -5,25 +5,42 @@ use serenity::http::Http;
use serenity::model::prelude::ChannelId;
use songbird::input::Metadata;
use crate::utils::error::BotResult;
use crate::commands::music::{get_queue_for_guild, get_voice_manager, is_dj};
use crate::utils::error::*;
use bot_serenityutils::core::MessageHandle;
use bot_serenityutils::menu::MenuBuilder;
use bot_serenityutils::error::SerenityUtilsResult;
use bot_serenityutils::menu::{Menu, MenuBuilder};
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::channel::Reaction;
use std::time::Duration;
use tokio::sync::RwLock;
static PAUSE_BUTTON: &str = "⏯️";
static SKIP_BUTTON: &str = "⏭️";
static STOP_BUTTON: &str = "⏹️";
/// Creates a new now playing message and returns the embed for that message
pub async fn create_now_playing_msg(
ctx: &Context,
channel_id: ChannelId,
meta: &Metadata,
paused: bool,
) -> BotResult<Arc<RwLock<MessageHandle>>> {
log::debug!("Creating now playing message");
let mut page = CreateMessage::default();
page.embed(|e| create_now_playing_embed(meta, e));
page.embed(|e| create_now_playing_embed(meta, e, paused));
let handle = MenuBuilder::default()
.add_control(0, STOP_BUTTON, |c, m, r| {
Box::pin(stop_button_action(c, m, r))
})
.add_control(1, PAUSE_BUTTON, |c, m, r| {
Box::pin(play_pause_button_action(c, m, r))
})
.add_control(2, SKIP_BUTTON, |c, m, r| {
Box::pin(skip_button_action(c, m, r))
})
.add_page(page)
.sticky(true)
.timeout(Duration::from_secs(60 * 60 * 24))
@ -38,12 +55,15 @@ pub async fn update_now_playing_msg(
http: &Arc<Http>,
handle: &Arc<RwLock<MessageHandle>>,
meta: &Metadata,
paused: bool,
) -> BotResult<()> {
log::debug!("Updating now playing message");
let handle = handle.read().await;
let mut message = handle.get_message(http).await?;
message
.edit(http, |m| m.embed(|e| create_now_playing_embed(meta, e)))
.edit(http, |m| {
m.embed(|e| create_now_playing_embed(meta, e, paused))
})
.await?;
Ok(())
@ -53,13 +73,16 @@ pub async fn update_now_playing_msg(
fn create_now_playing_embed<'a>(
meta: &Metadata,
mut embed: &'a mut CreateEmbed,
paused: bool,
) -> &'a mut CreateEmbed {
embed = embed.description(format!(
"Now Playing [{}]({}) by {}",
meta.title.clone().unwrap(),
meta.source_url.clone().unwrap(),
meta.artist.clone().unwrap()
));
embed = embed
.title(if paused { "Paused" } else { "Playing" })
.description(format!(
"[{}]({}) by {}",
meta.title.clone().unwrap(),
meta.source_url.clone().unwrap(),
meta.artist.clone().unwrap()
));
if let Some(thumb) = meta.thumbnail.clone() {
embed = embed.thumbnail(thumb);
@ -67,3 +90,98 @@ fn create_now_playing_embed<'a>(
embed
}
/// Toggled when the pause button is pressed
async fn play_pause_button_action(
ctx: &Context,
_: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let mut queue = queue.lock().await;
queue.pause();
let message = queue.now_playing_msg.clone().unwrap();
if let Some(current) = queue.current() {
update_now_playing_msg(&ctx.http, &message, current.metadata(), queue.paused()).await?;
}
}
Ok(())
}
/// Triggered when the skip button is pressed
async fn skip_button_action(
ctx: &Context,
_: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await;
if let Some(current) = queue.current() {
let _ = current.stop();
}
}
Ok(())
}
/// Triggered when the stop button is pressed
async fn stop_button_action(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
{
let manager = get_voice_manager(ctx).await;
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await;
let handler = manager.get(guild_id);
if let Some(handler) = handler {
let mut handler_lock = handler.lock().await;
handler_lock.remove_all_global_events();
}
if let Some(current) = queue.current() {
current.stop().map_err(BotError::from)?;
}
if manager.get(guild_id).is_some() {
manager.remove(guild_id).await.map_err(BotError::from)?;
log::debug!("Left the voice channel");
} else {
log::debug!("Not in a voice channel");
}
}
{
let handle = &menu.message;
let handle = handle.read().await;
ctx.http
.delete_message(handle.channel_id, handle.message_id)
.await?;
}
Ok(())
}

@ -1,3 +1,4 @@
use bot_serenityutils::error::SerenityUtilsError;
use thiserror::Error;
pub type BotResult<T> = Result<T, BotError>;
@ -34,6 +35,12 @@ pub enum BotError {
#[error("Serenity Utils Error: {0}")]
SerenityUtils(#[from] bot_serenityutils::error::SerenityUtilsError),
#[error("Track Error: {0}")]
TrackError(#[from] songbird::error::TrackError),
#[error("JoinError: {0}")]
JoinError(#[from] songbird::error::JoinError),
#[error("{0}")]
Msg(String),
}
@ -43,3 +50,9 @@ impl From<&str> for BotError {
Self::Msg(s.to_string())
}
}
impl From<BotError> for SerenityUtilsError {
fn from(e: BotError) -> Self {
Self::Msg(format!("{:?}", e))
}
}

Loading…
Cancel
Save