interaction_collector: rename from Paginator, implement Guard, is now generic

This commit is contained in:
Natsu Kagami 2025-02-22 16:37:48 +01:00 committed by Natsu Kagami
parent a35563801d
commit e777d4d634
4 changed files with 97 additions and 72 deletions

View file

@ -0,0 +1,90 @@
use crate::{Context, Result};
use serenity::{
all::{CreateInteractionResponse, Interaction, MessageId},
prelude::TypeMapKey,
};
use std::{ops::Deref, sync::Arc};
#[derive(Debug, Clone)]
/// Handles distributing interaction to the handlers.
pub struct InteractionCollector {
pub(crate) channels: Arc<dashmap::DashMap<MessageId, flume::Sender<String>>>,
}
/// Wraps the interfaction receiver channel, automatically cleaning up upon drop.
#[derive(Debug)]
struct InteractionCollectorGuard {
msg_id: MessageId,
ch: flume::Receiver<String>,
collector: InteractionCollector,
}
impl Deref for InteractionCollectorGuard {
type Target = flume::Receiver<String>;
fn deref(&self) -> &Self::Target {
&self.ch
}
}
impl Drop for InteractionCollectorGuard {
fn drop(&mut self) {
self.collector.channels.remove(&self.msg_id);
}
}
impl InteractionCollector {
pub fn new() -> Self {
Self {
channels: Arc::new(dashmap::DashMap::new()),
}
}
/// Create a new collector, returning a receiver.
pub fn create_collector(&self, msg: MessageId) -> impl Deref<Target = flume::Receiver<String>> {
let (send, recv) = flume::unbounded();
self.channels.insert(msg.clone(), send);
InteractionCollectorGuard {
msg_id: msg,
ch: recv,
collector: self.clone(),
}
}
/// Create a new collector, returning a receiver.
pub(crate) async fn create(
ctx: &Context,
msg: MessageId,
) -> Result<impl Deref<Target = flume::Receiver<String>>> {
Ok(ctx
.data
.read()
.await
.get::<InteractionCollector>()
.unwrap()
.create_collector(msg))
}
}
impl TypeMapKey for InteractionCollector {
type Value = InteractionCollector;
}
#[async_trait::async_trait]
impl crate::hook::InteractionHook for InteractionCollector {
async fn call(&self, ctx: &Context, interaction: &Interaction) -> Result<()> {
match interaction {
Interaction::Component(component_interaction) => {
if let Some(ch) = self.channels.get(&component_interaction.message.id) {
component_interaction
.create_response(ctx, CreateInteractionResponse::Acknowledge)
.await?;
ch.send_async(component_interaction.data.custom_id.clone())
.await
.ok();
}
Ok(())
}
_ => Ok(()),
}
}
}

View file

@ -17,6 +17,7 @@ pub use args::{ChannelId, Duration, RoleId, UserId, UsernameArg};
pub use debugging_ok::OkPrint;
pub use flags::Flags;
pub use hook::Hook;
pub use interaction_collector::InteractionCollector;
pub use member_cache::MemberCache;
pub use pagination::{paginate, paginate_from_fn, paginate_reply, CanEdit, Paginate};
pub use poise::CreateReply;
@ -25,6 +26,7 @@ pub mod announcer;
pub mod args;
pub mod flags;
pub mod hook;
pub mod interaction_collector;
pub mod member_cache;
pub mod pagination;
pub mod ratelimit;

View file

@ -3,17 +3,11 @@ use futures_util::future::Future;
use poise::{CreateReply, ReplyHandle};
use serenity::{
all::{
ComponentInteraction, CreateActionRow, CreateButton, CreateInteractionResponse,
EditInteractionResponse, EditMessage, Interaction, MessageId,
ComponentInteraction, CreateActionRow, CreateButton, EditInteractionResponse, EditMessage,
},
builder::CreateMessage,
model::{
channel::{Message, ReactionType},
id::ChannelId,
},
prelude::TypeMapKey,
model::{channel::Message, id::ChannelId},
};
use std::{convert::TryFrom, sync::Arc};
use tokio::time as tokio_time;
// const ARROW_RIGHT: &str = "➡️";
@ -264,8 +258,7 @@ pub async fn paginate_with_first_message(
timeout: std::time::Duration,
) -> Result<()> {
let msg_id = message.get_message().await?.id;
let (send, recv) = flume::unbounded::<String>();
Paginator::push(ctx, msg_id, send).await?;
let recv = crate::InteractionCollector::create(ctx, msg_id).await?;
do_render(&mut pager, 0, &mut message).await?;
// Just quit if there is only one page
@ -296,7 +289,6 @@ pub async fn paginate_with_first_message(
do_render_with_btns(&mut pager, page, &mut message, vec![])
.await
.pls_ok();
Paginator::pop(ctx, msg_id).await?;
res
}
@ -331,62 +323,3 @@ pub async fn handle_pagination_reaction(
page
})
}
#[derive(Debug, Clone)]
/// Handles distributing pagination interaction to the handlers.
pub struct Paginator {
pub(crate) channels: Arc<dashmap::DashMap<MessageId, flume::Sender<String>>>,
}
impl Paginator {
pub fn new() -> Self {
Self {
channels: Arc::new(dashmap::DashMap::new()),
}
}
async fn push(ctx: &Context, msg: MessageId, channel: flume::Sender<String>) -> Result<()> {
ctx.data
.read()
.await
.get::<Paginator>()
.unwrap()
.channels
.insert(msg, channel);
Ok(())
}
async fn pop(ctx: &Context, msg: MessageId) -> Result<()> {
ctx.data
.read()
.await
.get::<Paginator>()
.unwrap()
.channels
.remove(&msg);
Ok(())
}
}
impl TypeMapKey for Paginator {
type Value = Paginator;
}
#[async_trait::async_trait]
impl crate::hook::InteractionHook for Paginator {
async fn call(&self, ctx: &Context, interaction: &Interaction) -> Result<()> {
match interaction {
Interaction::Component(component_interaction) => {
if let Some(ch) = self.channels.get(&component_interaction.message.id) {
component_interaction
.create_response(ctx, CreateInteractionResponse::Acknowledge)
.await?;
ch.send_async(component_interaction.data.custom_id.clone())
.await
.ok();
}
Ok(())
}
_ => Ok(()),
}
}
}

View file

@ -237,9 +237,9 @@ async fn main() {
};
// Paginator
let paginator = youmubot_prelude::pagination::Paginator::new();
let paginator = youmubot_prelude::InteractionCollector::new();
handler.push_interaction_hook(paginator.clone());
data.insert::<youmubot_prelude::pagination::Paginator>(paginator);
data.insert::<youmubot_prelude::InteractionCollector>(paginator);
data.insert::<Env>(env.clone());