Implement custom embed menu in subcrate

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/8/head
trivernis 3 years ago
parent 4d290a5091
commit dc8a84c51e
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

59
Cargo.lock generated

@ -203,6 +203,17 @@ dependencies = [
"tokio-diesel",
]
[[package]]
name = "bot-serenityutils"
version = "0.1.0"
dependencies = [
"futures",
"log 0.4.14",
"serenity",
"thiserror",
"tokio",
]
[[package]]
name = "bumpalo"
version = "3.6.1"
@ -571,9 +582,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
dependencies = [
"futures-channel",
"futures-core",
@ -586,9 +597,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
dependencies = [
"futures-core",
"futures-sink",
@ -596,15 +607,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
[[package]]
name = "futures-executor"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1"
checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
dependencies = [
"futures-core",
"futures-task",
@ -613,15 +624,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
[[package]]
name = "futures-macro"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7"
checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b"
dependencies = [
"proc-macro-hack",
"proc-macro2",
@ -631,21 +642,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
[[package]]
name = "futures-task"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
[[package]]
name = "futures-util"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
dependencies = [
"futures-channel",
"futures-core",
@ -1951,16 +1962,6 @@ dependencies = [
"serde_repr",
]
[[package]]
name = "serenity_utils"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19368e88b3d6183b52704a2fbc1e5788ad4100876eddaa7217e1c8e1d2d2535c"
dependencies = [
"serenity",
"tokio",
]
[[package]]
name = "sha-1"
version = "0.9.4"
@ -2285,6 +2286,7 @@ dependencies = [
"aspotify",
"bot-coreutils",
"bot-database",
"bot-serenityutils",
"chrono",
"chrono-tz",
"colored",
@ -2302,7 +2304,6 @@ dependencies = [
"serde_derive",
"serde_json",
"serenity",
"serenity_utils",
"songbird",
"sysinfo",
"thiserror",

@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
bot-database = {path="./bot-database"}
bot-coreutils = {path="./bot-coreutils"}
bot-serenityutils = {path="./bot-serenityutils"}
serenity = "0.10.5"
dotenv = "0.15.0"
tokio = { version = "1.4.0", features = ["macros", "rt-multi-thread"] }
@ -31,4 +32,3 @@ sysinfo = "0.16.5"
reqwest = "0.11.2"
chrono-tz = "0.5.3"
sauce-api = "0.7.1"
serenity_utils = "0.6.1"

@ -0,0 +1 @@
target

File diff suppressed because it is too large Load Diff

@ -0,0 +1,14 @@
[package]
name = "bot-serenityutils"
version = "0.1.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serenity = "0.10.5"
tokio = "1.4.0"
thiserror = "1.0.24"
log = "0.4.14"
futures = "0.3.14"

@ -0,0 +1,4 @@
use crate::menu::traits::EventDrivenMessage;
pub type MessageHandle = (u64, u64);
pub type BoxedEventDrivenMessage = Box<dyn EventDrivenMessage>;

@ -0,0 +1,15 @@
use thiserror::Error;
pub type SerenityUtilsResult<T> = Result<T, SerenityUtilsError>;
#[derive(Debug, Error)]
pub enum SerenityUtilsError {
#[error("Serenity Error: {0}")]
SerenityError(#[from] serenity::Error),
#[error("Page {0} not found")]
PageNotFound(usize),
#[error("Serenity Utils not fully initialized")]
Uninitialized,
}

@ -0,0 +1,3 @@
pub mod core;
pub mod error;
pub mod menu;

@ -0,0 +1,179 @@
use crate::core::{BoxedEventDrivenMessage, MessageHandle};
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
use serenity::client::Context;
use serenity::model::prelude::*;
use serenity::prelude::TypeMapKey;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
/// Container to store event driven messages in the serenity context data
pub struct EventDrivenMessageContainer;
pub type MessageRef = Arc<Mutex<BoxedEventDrivenMessage>>;
pub type EventDrivenMessagesRef = Arc<Mutex<HashMap<MessageHandle, MessageRef>>>;
impl TypeMapKey for EventDrivenMessageContainer {
type Value = EventDrivenMessagesRef;
}
/// Starts the loop to handle message updates
pub async fn start_update_loop(ctx: &Context) {
let event_messages = get_listeners_from_context(ctx)
.await
.expect("Failed to get event message container");
let http = Arc::clone(&ctx.http);
tokio::task::spawn(async move {
loop {
{
log::trace!("Locking listener from update loop.");
let messages = {
let msgs_lock = event_messages.lock().await;
msgs_lock
.iter()
.map(|(k, v)| (*k, v.clone()))
.collect::<Vec<(MessageHandle, MessageRef)>>()
};
log::trace!("Listener locked.");
let mut frozen_messages = Vec::new();
for (key, msg) in messages {
let mut msg = msg.lock().await;
if let Err(e) = msg.update(&http).await {
log::error!("Failed to update message: {:?}", e);
}
if msg.is_frozen() {
frozen_messages.push(key);
}
}
{
let mut msgs_lock = event_messages.lock().await;
for key in frozen_messages {
msgs_lock.remove(&key);
}
}
}
log::trace!("Listener unlocked");
tokio::time::sleep(Duration::from_secs(10)).await;
}
});
}
/// To be fired from the serenity handler when a message was deleted
pub async fn handle_message_delete(
ctx: &Context,
channel_id: ChannelId,
message_id: MessageId,
) -> SerenityUtilsResult<()> {
let mut affected_messages = Vec::new();
{
let listeners = get_listeners_from_context(ctx).await?;
log::trace!("Locking listener from handle_message_delete.");
let mut listeners_lock = listeners.lock().await;
log::trace!("Listener locked.");
if let Some(msg) = listeners_lock.get(&(channel_id.0, message_id.0)) {
affected_messages.push(Arc::clone(msg));
listeners_lock.remove(&(channel_id.0, message_id.0));
}
}
log::trace!("Listener unlocked");
for msg in affected_messages {
let mut msg = msg.lock().await;
msg.on_deleted(ctx).await?;
}
Ok(())
}
/// To be fired from the serenity handler when multiple messages were deleted
pub async fn handle_message_delete_bulk(
ctx: &Context,
channel_id: ChannelId,
message_ids: &Vec<MessageId>,
) -> SerenityUtilsResult<()> {
let mut affected_messages = Vec::new();
{
let listeners = get_listeners_from_context(ctx).await?;
log::trace!("Locking listener from handle_message_delete_bulk.");
let mut listeners_lock = listeners.lock().await;
log::trace!("Listener locked.");
for message_id in message_ids {
if let Some(msg) = listeners_lock.get_mut(&(channel_id.0, message_id.0)) {
affected_messages.push(Arc::clone(msg));
listeners_lock.remove(&(channel_id.0, message_id.0));
}
}
}
log::trace!("Listener unlocked");
for msg in affected_messages {
let mut msg = msg.lock().await;
msg.on_deleted(ctx).await?;
}
Ok(())
}
/// Fired when a reaction was added to a message
pub async fn handle_reaction_add(ctx: &Context, reaction: &Reaction) -> SerenityUtilsResult<()> {
let mut affected_messages = Vec::new();
{
let listeners = get_listeners_from_context(ctx).await?;
log::trace!("Locking listener from handle_reaction_add.");
let mut listeners_lock = listeners.lock().await;
log::trace!("Listener locked.");
let message_id = reaction.message_id;
let channel_id = reaction.channel_id;
if let Some(msg) = listeners_lock.get_mut(&(channel_id.0, message_id.0)) {
affected_messages.push(Arc::clone(&msg));
}
}
log::trace!("Listener unlocked");
for msg in affected_messages {
let mut msg = msg.lock().await;
msg.on_reaction_add(ctx, reaction.clone()).await?;
}
Ok(())
}
/// Fired when a reaction was added to a message
pub async fn handle_reaction_remove(ctx: &Context, reaction: &Reaction) -> SerenityUtilsResult<()> {
let mut affected_messages = Vec::new();
{
let listeners = get_listeners_from_context(ctx).await?;
log::trace!("Locking listener from handle_reaction_remove.");
let mut listeners_lock = listeners.lock().await;
log::trace!("Listener locked.");
let message_id = reaction.message_id;
let channel_id = reaction.channel_id;
if let Some(msg) = listeners_lock.get_mut(&(channel_id.0, message_id.0)) {
affected_messages.push(Arc::clone(&msg));
}
}
log::trace!("Listener unlocked");
for msg in affected_messages {
let mut msg = msg.lock().await;
msg.on_reaction_remove(ctx, reaction.clone()).await?;
}
Ok(())
}
pub async fn get_listeners_from_context(
ctx: &Context,
) -> SerenityUtilsResult<EventDrivenMessagesRef> {
let data = ctx.data.read().await;
let listeners = data
.get::<EventDrivenMessageContainer>()
.ok_or(SerenityUtilsError::Uninitialized)?;
log::trace!("Returning listener");
Ok(listeners.clone())
}

@ -0,0 +1,61 @@
use crate::error::{SerenityUtilsError, SerenityUtilsResult};
use crate::menu::container::get_listeners_from_context;
use crate::menu::menu::Menu;
use serenity::client::Context;
use serenity::http::CacheHttp;
use serenity::model::channel::Reaction;
/// Shows the next page in the menu
pub async fn next_page(ctx: &Context, menu: &mut Menu<'_>, _: Reaction) -> SerenityUtilsResult<()> {
menu.current_page = (menu.current_page + 1) % menu.pages.len();
display_page(ctx, menu).await?;
Ok(())
}
/// Shows the previous page in the menu
pub async fn previous_page(
ctx: &Context,
menu: &mut Menu<'_>,
_: Reaction,
) -> SerenityUtilsResult<()> {
if menu.current_page == 0 {
menu.current_page = menu.pages.len() - 1;
} else {
menu.current_page = menu.current_page - 1;
}
display_page(ctx, menu).await?;
Ok(())
}
/// Shows the previous page in the menu
pub async fn close_menu(
ctx: &Context,
menu: &mut Menu<'_>,
_: Reaction,
) -> SerenityUtilsResult<()> {
menu.close(ctx.http()).await?;
let listeners = get_listeners_from_context(&ctx).await?;
let mut listeners_lock = listeners.lock().await;
listeners_lock.remove(&menu.message);
Ok(())
}
/// Displays the menu page
async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> {
let page = menu
.pages
.get(menu.current_page)
.ok_or(SerenityUtilsError::PageNotFound(menu.current_page))?;
let mut msg = menu.get_message(ctx).await?;
msg.edit(ctx, |e| {
e.0.clone_from(&mut page.0.clone());
e
})
.await?;
Ok(())
}

@ -0,0 +1,287 @@
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::traits::EventDrivenMessage;
use futures::FutureExt;
use serenity::async_trait;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::http::Http;
use serenity::model::channel::{Message, Reaction, ReactionType};
use serenity::model::id::ChannelId;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
pub static NEXT_PAGE_EMOJI: &str = "➡️";
pub static PREVIOUS_PAGE_EMOJI: &str = "⬅️";
pub static CLOSE_MENU_EMOJI: &str = "❌";
pub type ControlAction = Arc<
dyn for<'b> Fn(
&'b Context,
&'b mut Menu<'_>,
Reaction,
) -> Pin<Box<dyn Future<Output = SerenityUtilsResult<()>> + Send + 'b>>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct ActionContainer {
inner: ControlAction,
position: usize,
}
impl ActionContainer {
/// Creates a new control action
pub fn new(position: usize, inner: ControlAction) -> Self {
Self { inner, position }
}
/// Runs the action
pub async fn run(
&self,
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
self.inner.clone()(ctx, menu, reaction).await?;
Ok(())
}
}
/// A menu message
pub struct Menu<'a> {
pub message: MessageHandle,
pub pages: Vec<CreateMessage<'a>>,
pub current_page: usize,
pub controls: HashMap<String, ActionContainer>,
pub timeout: Instant,
closed: bool,
}
impl<'a> Menu<'a> {
/// Removes all reactions from the menu
pub(crate) async fn close(&mut self, http: &Http) -> SerenityUtilsResult<()> {
log::debug!("Closing menu...");
http.delete_message_reactions(self.message.0, self.message.1)
.await?;
self.closed = true;
Ok(())
}
/// Returns the message of the menu
pub async fn get_message(&self, ctx: &Context) -> SerenityUtilsResult<Message> {
let msg = ctx.http.get_message(self.message.0, self.message.1).await?;
Ok(msg)
}
}
#[async_trait]
impl<'a> EventDrivenMessage for Menu<'a> {
fn is_frozen(&self) -> bool {
self.closed
}
async fn update(&mut self, http: &Http) -> SerenityUtilsResult<()> {
log::trace!("Checking for menu timeout");
if Instant::now() >= self.timeout {
log::debug!("Menu timout reached. Closing menu.");
self.close(http).await?;
}
Ok(())
}
async fn on_deleted(&mut self, _: &Context) -> SerenityUtilsResult<()> {
Ok(())
}
async fn on_reaction_add(
&mut self,
ctx: &Context,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
log::debug!("Reaction to menu added");
let current_user = ctx.http.get_current_user().await?;
if reaction.user_id.unwrap().0 == current_user.id.0 {
log::debug!("Reaction is from current user.");
return Ok(());
}
let emoji_string = reaction.emoji.as_data();
log::debug!("Deleting user reaction.");
reaction.delete(ctx).await?;
if let Some(control) = self.controls.get(&emoji_string).cloned() {
log::debug!("Running control");
control.run(ctx, self, reaction).await?;
}
Ok(())
}
async fn on_reaction_remove(&mut self, _: &Context, _: Reaction) -> SerenityUtilsResult<()> {
Ok(())
}
}
/// A builder for messages
pub struct MenuBuilder {
pages: Vec<CreateMessage<'static>>,
current_page: usize,
controls: HashMap<String, ActionContainer>,
timeout: Duration,
}
impl Default for MenuBuilder {
fn default() -> Self {
Self {
pages: vec![],
current_page: 0,
controls: HashMap::new(),
timeout: Duration::from_secs(60),
}
}
}
impl MenuBuilder {
/// Creates a new paginaton menu
pub fn new_paginator() -> Self {
log::debug!("Creating new paginator");
let mut controls = HashMap::new();
controls.insert(
PREVIOUS_PAGE_EMOJI.to_string(),
ActionContainer::new(0, Arc::new(|c, m, r| previous_page(c, m, r).boxed())),
);
controls.insert(
CLOSE_MENU_EMOJI.to_string(),
ActionContainer::new(1, Arc::new(|c, m, r| close_menu(c, m, r).boxed())),
);
controls.insert(
NEXT_PAGE_EMOJI.to_string(),
ActionContainer::new(2, Arc::new(|c, m, r| next_page(c, m, r).boxed())),
);
Self {
controls,
..Default::default()
}
}
/// Adds a page to the message builder
pub fn add_page(mut self, page: CreateMessage<'static>) -> Self {
self.pages.push(page);
self
}
/// Adds multiple pages to the message
pub fn add_pages<I>(mut self, pages: I) -> Self
where
I: IntoIterator<Item = CreateMessage<'static>>,
{
let mut pages = pages.into_iter().collect();
self.pages.append(&mut pages);
self
}
/// Adds a single control to the message
pub fn add_control<S: ToString>(
mut self,
position: usize,
emoji: S,
action: ControlAction,
) -> Self {
self.controls
.insert(emoji.to_string(), ActionContainer::new(position, action));
self
}
/// Adds a single control to the message
pub fn add_controls<S, I>(mut self, controls: I) -> Self
where
S: ToString,
I: IntoIterator<Item = (usize, S, ControlAction)>,
{
for (position, emoji, action) in controls {
self.controls
.insert(emoji.to_string(), ActionContainer::new(position, action));
}
self
}
/// Sets the timeout for the message
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
/// Sets the start page of the message
pub fn start_page(mut self, page: usize) -> Self {
self.current_page = page;
self
}
/// builds the menu
pub async fn build(self, ctx: &Context, channel_id: ChannelId) -> SerenityUtilsResult<()> {
log::debug!("Building menu...");
let mut current_page = self
.pages
.get(self.current_page)
.ok_or(SerenityUtilsError::PageNotFound(self.current_page))?
.clone();
let message = channel_id.send_message(ctx, |_| &mut current_page).await?;
log::trace!("Message is {:?}", message);
let listeners = get_listeners_from_context(ctx).await?;
log::debug!("Sorting controls...");
let mut controls = self
.controls
.clone()
.into_iter()
.collect::<Vec<(String, ActionContainer)>>();
controls.sort_by_key(|(_, a)| a.position);
log::debug!("Creating menu...");
let menu = Menu {
message: (message.channel_id.0, message.id.0),
pages: self.pages,
current_page: self.current_page,
controls: self.controls,
timeout: Instant::now() + self.timeout,
closed: false,
};
log::debug!("Storing menu to listeners...");
{
let mut listeners_lock = listeners.lock().await;
log::trace!("Listeners locked.");
listeners_lock.insert(
(message.channel_id.0, message.id.0),
Arc::new(Mutex::new(Box::new(menu))),
);
}
log::debug!("Adding controls...");
for (emoji, _) in controls {
message
.react(ctx, ReactionType::Unicode(emoji.clone()))
.await?;
}
log::debug!("Menu successfully created.");
Ok(())
}
}

@ -0,0 +1,12 @@
pub(crate) mod container;
pub(crate) mod controls;
pub(crate) mod menu;
pub(crate) mod traits;
pub use container::*;
pub use controls::*;
pub use menu::{
ActionContainer, ControlAction, Menu, MenuBuilder, CLOSE_MENU_EMOJI, NEXT_PAGE_EMOJI,
PREVIOUS_PAGE_EMOJI,
};
pub use traits::EventDrivenMessage;

@ -0,0 +1,30 @@
use crate::error::SerenityUtilsResult;
use serenity::client::Context;
use serenity::http::Http;
use serenity::{async_trait, model::prelude::*};
#[async_trait]
pub trait EventDrivenMessage: Send + Sync {
/// Returns if a message has been frozen and won't handle any further events
fn is_frozen(&self) -> bool;
/// Fired periodically
async fn update(&mut self, http: &Http) -> SerenityUtilsResult<()>;
/// Fired when the message was deleted
async fn on_deleted(&mut self, ctx: &Context) -> SerenityUtilsResult<()>;
/// Fired when a reaction was added to the message
async fn on_reaction_add(
&mut self,
ctx: &Context,
reaction: Reaction,
) -> SerenityUtilsResult<()>;
/// Fired when a reaction was removed from the message
async fn on_reaction_remove(
&mut self,
ctx: &Context,
reaction: Reaction,
) -> SerenityUtilsResult<()>;
}

@ -12,10 +12,11 @@ use songbird::SerenityInit;
use crate::commands::*;
use crate::handler::Handler;
use crate::utils::context_data::{
DatabaseContainer, EventDrivenMessageContainer, Store, StoreData,
};
use crate::utils::context_data::{DatabaseContainer, Store, StoreData};
use crate::utils::error::{BotError, BotResult};
use bot_serenityutils::menu::EventDrivenMessageContainer;
use std::sync::Arc;
use tokio::sync::Mutex;
pub async fn get_client() -> BotResult<Client> {
let token = dotenv::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;
@ -30,7 +31,7 @@ pub async fn get_client() -> BotResult<Client> {
let mut data = client.data.write().await;
data.insert::<Store>(StoreData::new());
data.insert::<DatabaseContainer>(database);
data.insert::<EventDrivenMessageContainer>(HashMap::new());
data.insert::<EventDrivenMessageContainer>(Arc::new(Mutex::new(HashMap::new())));
}
Ok(client)

@ -9,20 +9,76 @@ use serenity::model::voice::VoiceState;
use serenity::prelude::*;
use crate::commands::music::get_queue_for_guild;
use crate::utils::context_data::EventDrivenMessageContainer;
use bot_serenityutils::menu::{
handle_message_delete, handle_message_delete_bulk, handle_reaction_add, handle_reaction_remove,
start_update_loop,
};
pub(crate) struct Handler;
macro_rules! log_msg_fire_error {
($msg:expr) => {
if let Err(e) = $msg {
log::error!("Failed to handle event for message: {:?}", e);
}
};
}
#[async_trait]
impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, _: Vec<GuildId>) {
log::info!("Cache Ready");
start_update_loop(&ctx).await;
}
/// Fired when a message was deleted
async fn message_delete(
&self,
ctx: Context,
channel_id: ChannelId,
message_id: MessageId,
_: Option<GuildId>,
) {
tokio::spawn(async move {
log::trace!("Handling message delete event");
if let Err(e) = handle_message_delete(&ctx, channel_id, message_id).await {
log::error!("Failed to handle event: {:?}", e);
}
log::trace!("Message delete event handled");
});
}
/// Fired when multiple messages were deleted
async fn message_delete_bulk(
&self,
ctx: Context,
channel_id: ChannelId,
message_ids: Vec<MessageId>,
_: Option<GuildId>,
) {
tokio::spawn(async move {
log::trace!("Handling message delete bulk event");
if let Err(e) = handle_message_delete_bulk(&ctx, channel_id, &message_ids).await {
log::error!("Failed to handle event: {:?}", e);
}
log::debug!("Message delte bulk event handled");
});
}
/// Fired when a reaction was added to a message
async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
tokio::spawn(async move {
log::trace!("Handling reaction add event...");
if let Err(e) = handle_reaction_add(&ctx, &reaction).await {
log::error!("Failed to handle event: {:?}", e);
}
log::trace!("Reaction add event handled");
});
}
/// Fired when a reaction was added to a message
async fn reaction_remove(&self, ctx: Context, reaction: Reaction) {
tokio::spawn(async move {
log::trace!("Handling reaction remove event");
if let Err(e) = handle_reaction_remove(&ctx, &reaction).await {
log::error!("Failed to handle event: {:?}", e);
}
log::trace!("Reaction remove event handled");
});
}
async fn ready(&self, ctx: Context, ready: Ready) {
log::info!("Connected as {}", ready.user.name);
let prefix = dotenv::var("BOT_PREFIX").unwrap_or("~!".to_string());
@ -66,67 +122,6 @@ impl EventHandler for Handler {
queue_lock.leave_flag = count == 0;
}
}
/// Fired when a message was deleted
async fn message_delete(
&self,
ctx: Context,
channel_id: ChannelId,
message_id: MessageId,
_: Option<GuildId>,
) {
let mut data = ctx.data.write().await;
let listeners = data.get_mut::<EventDrivenMessageContainer>().unwrap();
if let Some(msg) = listeners.get(&(channel_id.0, message_id.0)) {
log_msg_fire_error!(msg.on_deleted().await);
listeners.remove(&(channel_id.0, message_id.0));
}
}
/// Fired when multiple messages were deleted
async fn message_delete_bulk(
&self,
ctx: Context,
channel_id: ChannelId,
message_ids: Vec<MessageId>,
_: Option<GuildId>,
) {
let data = ctx.data.read().await;
let listeners = data.get::<EventDrivenMessageContainer>().unwrap();
for message_id in message_ids {
if let Some(msg) = listeners.get(&(channel_id.0, message_id.0)) {
log_msg_fire_error!(msg.on_deleted().await);
}
}
}
/// Fired when a reaction was added to a message
async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
let data = ctx.data.read().await;
let listeners = data.get::<EventDrivenMessageContainer>().unwrap();
let message_id = reaction.message_id;
let channel_id = reaction.channel_id;
if let Some(msg) = listeners.get(&(channel_id.0, message_id.0)) {
log_msg_fire_error!(msg.on_reaction_add(reaction).await);
}
}
/// Fired when a reaction was added to a message
async fn reaction_remove(&self, ctx: Context, reaction: Reaction) {
let data = ctx.data.read().await;
let listeners = data.get::<EventDrivenMessageContainer>().unwrap();
let message_id = reaction.message_id;
let channel_id = reaction.channel_id;
if let Some(msg) = listeners.get(&(channel_id.0, message_id.0)) {
log_msg_fire_error!(msg.on_reaction_remove(reaction).await);
}
}
}
/// Returns the number of members in the channel if it's the bots voice channel

@ -3,11 +3,12 @@ use std::cmp::Ordering;
use sauce_api::{SauceItem, SauceResult};
use serenity::builder::CreateMessage;
use serenity::{model::channel::Message, prelude::*};
use serenity_utils::prelude::*;
use bot_coreutils::url::get_domain_for_url;
use crate::utils::error::BotResult;
use bot_serenityutils::menu::MenuBuilder;
use std::time::Duration;
static MAX_RESULTS: usize = 6;
static MIN_SIMILARITY: f32 = 50.0;
@ -20,28 +21,19 @@ pub async fn show_sauce_menu(
) -> BotResult<()> {
let pages: Vec<CreateMessage> = sources.into_iter().map(create_sauce_page).collect();
let menu = if pages.len() == 1 {
Menu::new(
ctx,
msg,
&pages,
MenuOptions {
controls: vec![],
..Default::default()
},
)
if pages.len() == 1 {
MenuBuilder::default()
.timeout(Duration::from_secs(600))
.add_pages(pages)
.build(ctx, msg.channel_id)
.await?;
} else {
Menu::new(
ctx,
msg,
&pages,
MenuOptions {
timeout: 600.,
..Default::default()
},
)
MenuBuilder::new_paginator()
.timeout(Duration::from_secs(600))
.add_pages(pages)
.build(ctx, msg.channel_id)
.await?;
};
menu.run().await?;
Ok(())
}

@ -11,7 +11,6 @@ use tokio::sync::Mutex;
use crate::providers::music::queue::MusicQueue;
use crate::providers::music::spotify::SpotifyApi;
use crate::utils::messages::EventDrivenMessage;
pub struct Store;
@ -58,9 +57,3 @@ pub async fn get_database_from_context(ctx: &Context) -> Database {
database.clone()
}
pub struct EventDrivenMessageContainer;
impl TypeMapKey for EventDrivenMessageContainer {
type Value = HashMap<(u64, u64), Box<dyn EventDrivenMessage>>;
}

@ -32,7 +32,7 @@ pub enum BotError {
CliInject,
#[error("Serenity Utils Error: {0}")]
SerenityUtils(#[from] serenity_utils::Error),
SerenityUtils(#[from] bot_serenityutils::error::SerenityUtilsError),
#[error("{0}")]
Msg(String),

@ -1,20 +1,12 @@
use std::sync::Arc;
use serenity::async_trait;
use serenity::builder::{CreateMessage, EditMessage};
use serenity::http::{CacheHttp, Http};
use serenity::model::channel::{Message, Reaction};
use serenity::model::channel::Message;
use serenity::model::id::{ChannelId, MessageId};
use crate::utils::error::BotResult;
#[async_trait]
pub trait EventDrivenMessage: Send + Sync {
async fn on_deleted(&self) -> BotResult<()>;
async fn on_reaction_add(&self, reaction: Reaction) -> BotResult<()>;
async fn on_reaction_remove(&self, reaction: Reaction) -> BotResult<()>;
}
#[derive(Clone)]
pub struct ShareableMessage {
http: Arc<Http>,

Loading…
Cancel
Save