From bb153a10a53c6190d7c840f97b9166f283aee590 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 17 Apr 2021 14:16:05 +0200 Subject: [PATCH] Add help display for menus Signed-off-by: trivernis --- Cargo.lock | 5 +- Cargo.toml | 3 +- bot-serenityutils/src/menu/controls.rs | 68 ++++++++++++++++++++++++++ bot-serenityutils/src/menu/menu.rs | 61 ++++++++++++++++++++--- bot-serenityutils/src/menu/mod.rs | 1 + bot-serenityutils/src/menu/typedata.rs | 9 ++++ src/handler.rs | 9 ++-- src/messages/gifs.rs | 1 + src/messages/music/now_playing.rs | 8 +++ src/messages/sauce.rs | 1 + 10 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 bot-serenityutils/src/menu/typedata.rs diff --git a/Cargo.lock b/Cargo.lock index 67403fe..7fc4a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2347,6 +2347,7 @@ dependencies = [ "thiserror", "tokio", "trigram", + "typemap_rev", ] [[package]] @@ -2535,9 +2536,9 @@ dependencies = [ [[package]] name = "typemap_rev" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335fb14412163adc9ed4a3e53335afaa7a4b72bdd122e5f72f51b8f1db1a131e" +checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" [[package]] name = "typenum" diff --git a/Cargo.toml b/Cargo.toml index 9f2a3fc..5eb1d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,4 +36,5 @@ reqwest = "0.11.2" chrono-tz = "0.5.3" sauce-api = "0.7.1" rustc_version_runtime = "0.2.0" -trigram = "0.4.4" \ No newline at end of file +trigram = "0.4.4" +typemap_rev = "0.1.5" \ No newline at end of file diff --git a/bot-serenityutils/src/menu/controls.rs b/bot-serenityutils/src/menu/controls.rs index c76888d..37aac16 100644 --- a/bot-serenityutils/src/menu/controls.rs +++ b/bot-serenityutils/src/menu/controls.rs @@ -1,9 +1,13 @@ use crate::error::{SerenityUtilsError, SerenityUtilsResult}; use crate::menu::container::get_listeners_from_context; use crate::menu::menu::Menu; +use crate::menu::typedata::HelpActiveContainer; +use serde_json::json; +use serde_json::Value; use serenity::client::Context; use serenity::http::CacheHttp; use serenity::model::channel::Reaction; +use std::sync::atomic::Ordering; /// Shows the next page in the menu pub async fn next_page(ctx: &Context, menu: &mut Menu<'_>, _: Reaction) -> SerenityUtilsResult<()> { @@ -47,6 +51,70 @@ pub async fn close_menu( Ok(()) } +pub async fn toggle_help( + ctx: &Context, + menu: &mut Menu<'_>, + _: Reaction, +) -> SerenityUtilsResult<()> { + log::debug!("Displaying help"); + let show_help = menu + .data + .get::() + .expect("Missing HelpActiveContainer in menu data") + .clone(); + + if show_help.load(Ordering::Relaxed) { + display_page(ctx, menu).await?; + show_help.store(false, Ordering::Relaxed); + return Ok(()); + } + let page = menu + .pages + .get(menu.current_page) + .ok_or(SerenityUtilsError::PageNotFound(menu.current_page))? + .get() + .await?; + let mut message = menu.get_message(ctx.http()).await?; + let help_message = menu + .help_entries + .iter() + .map(|(e, h)| format!(" - {} {}", e, h)) + .collect::>() + .join("\n"); + + message + .edit(ctx, |m| { + m.0.clone_from(&mut page.0.clone()); + + if let Some(embed) = m.0.get_mut("embed") { + let embed = embed.as_object_mut().unwrap(); + let fields = embed + .entry("fields") + .or_insert_with(|| Value::Array(vec![])); + if let Value::Array(ref mut inner) = *fields { + inner.push(json!({ + "inline": false, + "name": "Help".to_string(), + "value": help_message, + })); + } + } else { + m.embed(|e| { + e.field("Help", help_message, false); + + e + }); + } + + m + }) + .await?; + log::debug!("Help message displayed"); + show_help.store(true, Ordering::Relaxed); + + Ok(()) +} + /// Displays the menu page async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> { log::debug!("Displaying page {}", menu.current_page); diff --git a/bot-serenityutils/src/menu/menu.rs b/bot-serenityutils/src/menu/menu.rs index b874b8c..787d555 100644 --- a/bot-serenityutils/src/menu/menu.rs +++ b/bot-serenityutils/src/menu/menu.rs @@ -1,8 +1,9 @@ use crate::core::MessageHandle; use crate::error::{SerenityUtilsError, SerenityUtilsResult}; use crate::menu::container::get_listeners_from_context; -use crate::menu::controls::{close_menu, next_page, previous_page}; +use crate::menu::controls::{close_menu, next_page, previous_page, toggle_help}; use crate::menu::traits::EventDrivenMessage; +use crate::menu::typedata::HelpActiveContainer; use crate::menu::{EventDrivenMessagesRef, Page}; use futures::FutureExt; use serenity::async_trait; @@ -10,9 +11,11 @@ use serenity::client::Context; use serenity::http::Http; use serenity::model::channel::{Message, Reaction, ReactionType}; use serenity::model::id::ChannelId; +use serenity::prelude::{TypeMap, TypeMapKey}; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; +use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{Mutex, RwLock}; @@ -20,6 +23,7 @@ use tokio::sync::{Mutex, RwLock}; pub static NEXT_PAGE_EMOJI: &str = "➡️"; pub static PREVIOUS_PAGE_EMOJI: &str = "⬅️"; pub static CLOSE_MENU_EMOJI: &str = "❌"; +pub static HELP_EMOJI: &str = "❔"; pub type ControlActionResult<'b> = Pin> + Send + 'b>>; @@ -33,12 +37,12 @@ pub type ControlActionArc = Arc< #[derive(Clone)] pub struct ActionContainer { inner: ControlActionArc, - position: usize, + position: isize, } impl ActionContainer { /// Creates a new control action - pub fn new(position: usize, callback: F) -> Self + pub fn new(position: isize, callback: F) -> Self where F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b> + Send @@ -63,7 +67,6 @@ impl ActionContainer { } /// A menu message -#[derive(Clone)] pub struct Menu<'a> { pub message: Arc>, pub pages: Vec>, @@ -71,6 +74,8 @@ pub struct Menu<'a> { pub controls: HashMap, pub timeout: Instant, pub sticky: bool, + pub data: TypeMap, + pub help_entries: HashMap, closed: bool, listeners: EventDrivenMessagesRef, } @@ -221,6 +226,8 @@ pub struct MenuBuilder { controls: HashMap, timeout: Duration, sticky: bool, + data: TypeMap, + help_entries: HashMap, } impl Default for MenuBuilder { @@ -231,6 +238,8 @@ impl Default for MenuBuilder { controls: HashMap::new(), timeout: Duration::from_secs(60), sticky: false, + data: TypeMap::new(), + help_entries: HashMap::new(), } } } @@ -240,21 +249,35 @@ impl MenuBuilder { pub fn new_paginator() -> Self { log::debug!("Creating new paginator"); let mut controls = HashMap::new(); + let mut help_entries = HashMap::new(); controls.insert( PREVIOUS_PAGE_EMOJI.to_string(), ActionContainer::new(0, |c, m, r| previous_page(c, m, r).boxed()), ); + help_entries.insert( + PREVIOUS_PAGE_EMOJI.to_string(), + "Displays the previous page".to_string(), + ); controls.insert( CLOSE_MENU_EMOJI.to_string(), ActionContainer::new(1, |c, m, r| close_menu(c, m, r).boxed()), ); + help_entries.insert( + CLOSE_MENU_EMOJI.to_string(), + "Closes the menu buttons".to_string(), + ); controls.insert( NEXT_PAGE_EMOJI.to_string(), ActionContainer::new(2, |c, m, r| next_page(c, m, r).boxed()), ); + help_entries.insert( + NEXT_PAGE_EMOJI.to_string(), + "Displays the next page".to_string(), + ); Self { controls, + help_entries, ..Default::default() } } @@ -278,7 +301,7 @@ impl MenuBuilder { } /// Adds a single control to the message - pub fn add_control(mut self, position: usize, emoji: S, action: F) -> Self + pub fn add_control(mut self, position: isize, emoji: S, action: F) -> Self where S: ToString, F: for<'b> Fn(&'b Context, &'b mut Menu<'_>, Reaction) -> ControlActionResult<'b> @@ -295,7 +318,7 @@ impl MenuBuilder { pub fn add_controls(mut self, controls: I) -> Self where S: ToString, - I: IntoIterator, + I: IntoIterator, { for (position, emoji, action) in controls { self.controls.insert( @@ -332,6 +355,30 @@ impl MenuBuilder { self } + /// Adds data to the menu typemap + pub fn add_data(mut self, value: T::Value) -> Self + where + T: TypeMapKey, + { + self.data.insert::(value); + + self + } + + /// Adds a help entry + pub fn add_help(mut self, button: S, help: S) -> Self { + self.help_entries + .insert(button.to_string(), help.to_string()); + + self + } + + /// Turns showing help for buttons on + pub fn show_help(self) -> Self { + self.add_control(100, HELP_EMOJI, |c, m, r| Box::pin(toggle_help(c, m, r))) + .add_data::(Arc::new(AtomicBool::new(false))) + } + /// builds the menu pub async fn build( self, @@ -371,6 +418,8 @@ impl MenuBuilder { closed: false, listeners: Arc::clone(&listeners), sticky: self.sticky, + data: self.data, + help_entries: self.help_entries, }; log::debug!("Storing menu to listeners..."); diff --git a/bot-serenityutils/src/menu/mod.rs b/bot-serenityutils/src/menu/mod.rs index 8eaffaf..f38518b 100644 --- a/bot-serenityutils/src/menu/mod.rs +++ b/bot-serenityutils/src/menu/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod controls; pub(crate) mod menu; pub(crate) mod page; pub(crate) mod traits; +pub(crate) mod typedata; pub use container::*; pub use controls::*; diff --git a/bot-serenityutils/src/menu/typedata.rs b/bot-serenityutils/src/menu/typedata.rs new file mode 100644 index 0000000..c2d6a59 --- /dev/null +++ b/bot-serenityutils/src/menu/typedata.rs @@ -0,0 +1,9 @@ +use serenity::prelude::TypeMapKey; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +pub struct HelpActiveContainer; + +impl TypeMapKey for HelpActiveContainer { + type Value = Arc; +} diff --git a/src/handler.rs b/src/handler.rs index 8c7bbb7..5bd3da4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -120,10 +120,11 @@ impl EventHandler for Handler { if let Some(count) = member_count { log::debug!("{} Members in channel", count); - let queue = get_queue_for_guild(&ctx, &guild_id).await.unwrap(); - let mut queue_lock = queue.lock().await; - log::debug!("Setting leave flag to {}", count == 0); - queue_lock.leave_flag = count == 0; + if let Ok(queue) = get_queue_for_guild(&ctx, &guild_id).await { + let mut queue_lock = queue.lock().await; + log::debug!("Setting leave flag to {}", count == 0); + queue_lock.leave_flag = count == 0; + } } } } diff --git a/src/messages/gifs.rs b/src/messages/gifs.rs index cf832d3..788f91d 100644 --- a/src/messages/gifs.rs +++ b/src/messages/gifs.rs @@ -22,6 +22,7 @@ pub async fn create_gifs_menu( MenuBuilder::new_paginator() .timeout(Duration::from_secs(120)) .add_pages(pages) + .show_help() .build(ctx, channel_id) .await?; diff --git a/src/messages/music/now_playing.rs b/src/messages/music/now_playing.rs index 508f092..c09631b 100644 --- a/src/messages/music/now_playing.rs +++ b/src/messages/music/now_playing.rs @@ -37,15 +37,23 @@ pub async fn create_now_playing_msg( .add_control(0, STOP_BUTTON, |c, m, r| { Box::pin(stop_button_action(c, m, r)) }) + .add_help(STOP_BUTTON, "Stops the music and leaves the channel") .add_control(1, PAUSE_BUTTON, |c, m, r| { Box::pin(play_pause_button_action(c, m, r)) }) + .add_help(PAUSE_BUTTON, "Pauses the music") .add_control(2, SKIP_BUTTON, |c, m, r| { Box::pin(skip_button_action(c, m, r)) }) + .add_help(SKIP_BUTTON, "Skips to the next song") .add_control(3, GOOD_PICK_BUTTON, |c, m, r| { Box::pin(good_pick_action(c, m, r)) }) + .add_help( + GOOD_PICK_BUTTON, + "Remembers this video for spotify-youtube mappings", + ) + .show_help() .add_page(Page::new_builder(move || { let queue = Arc::clone(&queue); Box::pin(async move { diff --git a/src/messages/sauce.rs b/src/messages/sauce.rs index e0aeb08..527ae24 100644 --- a/src/messages/sauce.rs +++ b/src/messages/sauce.rs @@ -38,6 +38,7 @@ pub async fn show_sauce_menu( MenuBuilder::new_paginator() .timeout(Duration::from_secs(600)) .add_pages(pages) + .show_help() .build(ctx, msg.channel_id) .await?; }