diff --git a/youmubot-prelude/src/lib.rs b/youmubot-prelude/src/lib.rs index c2c3d6b..77a31a1 100644 --- a/youmubot-prelude/src/lib.rs +++ b/youmubot-prelude/src/lib.rs @@ -3,11 +3,13 @@ use std::sync::Arc; pub mod announcer; pub mod args; +pub mod pagination; pub mod reaction_watch; pub mod setup; pub use announcer::Announcer; pub use args::Duration; +pub use pagination::Pagination; pub use reaction_watch::{ReactionHandler, ReactionWatcher}; /// The global app data. diff --git a/youmubot-prelude/src/pagination.rs b/youmubot-prelude/src/pagination.rs new file mode 100644 index 0000000..4025e18 --- /dev/null +++ b/youmubot-prelude/src/pagination.rs @@ -0,0 +1,119 @@ +use crate::{Context, ReactionHandler, ReactionWatcher}; +use serenity::{ + builder::EditMessage, + framework::standard::{CommandError, CommandResult}, + model::{ + channel::{Message, Reaction, ReactionType}, + id::ChannelId, + }, +}; + +impl ReactionWatcher { + /// Start a pagination. + /// + /// Takes a copy of Context (which you can `clone`), a pager (see "Pagination") and a target channel id. + /// Pagination will handle all events on adding/removing an "arrow" emoji (⬅️ and ➡️). + /// This is a blocking call - it will block the thread until duration is over. + pub fn paginate( + &self, + ctx: Context, + channel: ChannelId, + pager: T, + duration: std::time::Duration, + ) -> CommandResult { + let handler = PaginationHandler::new(pager, ctx, channel)?; + self.handle_reactions(handler, duration) + } +} + +/// Pagination allows the bot to display content in multiple pages. +/// +/// You need to implement the "render_page" function, which takes a dummy content and +/// embed assigning function. +/// Pagination is automatically implemented for functions with the same signature as `render_page`. +/// +/// Pages start at 0. +pub trait Pagination { + /// Render a page. + /// + /// This would either create or edit a message, but you should not be worry about it. + fn render_page<'a>( + &self, + page: u8, + target: &'a mut EditMessage, + ) -> (&'a mut EditMessage, CommandResult); +} + +impl Pagination for T +where + T: for<'a> Fn(u8, &'a mut EditMessage) -> (&'a mut EditMessage, CommandResult), +{ + fn render_page<'a>( + &self, + page: u8, + target: &'a mut EditMessage, + ) -> (&'a mut EditMessage, CommandResult) { + self(page, target) + } +} + +struct PaginationHandler { + pager: T, + message: Message, + page: u8, + ctx: Context, +} + +impl PaginationHandler { + pub fn new(pager: T, mut ctx: Context, channel: ChannelId) -> Result { + let message = channel.send_message(&mut ctx, |e| { + e.content("Youmu is loading the first page...") + })?; + // React to the message + message.react(&mut ctx, "⬅️")?; + message.react(&mut ctx, "➡️️")?; + let mut p = Self { + pager, + message: message.clone(), + page: 0, + ctx, + }; + p.call_pager()?; + Ok(p) + } +} + +impl PaginationHandler { + /// Call the pager, log the error (if any). + fn call_pager(&mut self) -> CommandResult { + let mut res: CommandResult = Ok(()); + let mut msg = self.message.clone(); + msg.edit(&self.ctx, |e| { + let (e, r) = self.pager.render_page(self.page, e); + res = r; + e + })?; + res + } +} + +impl ReactionHandler for PaginationHandler { + fn handle_reaction(&mut self, reaction: &Reaction, _is_add: bool) -> CommandResult { + match &reaction.emoji { + ReactionType::Unicode(ref s) => match s.as_str() { + "⬅" if self.page == 0 => return Ok(()), + "⬅" => { + self.page -= 1; + self.call_pager()?; + } + "➡" => { + self.page += 1; + self.call_pager()?; + } + _ => (), + }, + _ => (), + } + Ok(()) + } +}