Composed framework (#43)

Composed poise and serenity into a composed framework. This will help the migration towards slash-commands in the future.
This commit is contained in:
huynd2001 2024-03-27 12:50:48 -04:00 committed by GitHub
parent f3c062f417
commit a2f0684509
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 201 additions and 2 deletions

89
Cargo.lock generated
View file

@ -440,6 +440,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.48",
]
[[package]]
name = "darling_macro"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.48",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@ -481,6 +516,17 @@ dependencies = [
"serde",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -1013,6 +1059,12 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
@ -1543,6 +1595,35 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "poise"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6"
dependencies = [
"async-trait",
"derivative",
"futures-util",
"parking_lot",
"poise_macros",
"regex",
"serenity",
"tokio",
"tracing",
]
[[package]]
name = "poise_macros"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -2346,6 +2427,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.5.0"
@ -3106,6 +3193,7 @@ version = "0.1.0"
dependencies = [
"dotenv",
"env_logger",
"poise",
"serenity",
"tokio",
"youmubot-cf",
@ -3209,6 +3297,7 @@ dependencies = [
"dashmap",
"flume 0.10.14",
"futures-util",
"poise",
"reqwest",
"serenity",
"thiserror",

View file

@ -18,6 +18,7 @@ chrono = "0.4.19"
flume = "0.10.13"
dashmap = "5.3.4"
thiserror = "1"
poise = "0.6"
[dependencies.serenity]
version = "0.12"

View file

@ -33,6 +33,9 @@ pub mod table_format;
/// The global app data.
pub type AppData = Arc<RwLock<TypeMap>>;
/// The global context type for app commands
pub type CmdContext<'a, Env> = poise::Context<'a, Env, anyhow::Error>;
/// The HTTP client.
pub struct HTTPClient;

View file

@ -13,6 +13,7 @@ codeforces = ["youmubot-cf"]
[dependencies]
serenity = "0.12"
poise = "0.6"
tokio = { version = "1.19.2", features = ["rt-multi-thread"] }
dotenv = "0.15.0"
env_logger = "0.9.0"

View file

@ -0,0 +1,54 @@
use std::{future::Future, pin::Pin};
use serenity::{client::FullEvent, framework::Framework};
use youmubot_prelude::*;
/// A Framework to compose other frameworks.
pub(crate) struct ComposedFramework {
frameworks: Box<[Box<dyn Framework>]>,
}
impl ComposedFramework {
/// Create a new composed framework.
pub fn new(frameworks: Vec<Box<dyn Framework>>) -> Self {
Self {
frameworks: frameworks.into_boxed_slice(),
}
}
}
#[async_trait]
impl Framework for ComposedFramework {
async fn dispatch(&self, ctx: Context, msg: FullEvent) -> () {
if !self.frameworks.is_empty() {
self.dispatch_loop(self.frameworks.len() - 1, ctx, msg)
.await
}
}
async fn init(&mut self, client: &Client) {
for f in self.frameworks.iter_mut() {
f.init(&client).await
}
}
}
impl ComposedFramework {
/// Dispatch to all inner frameworks in a loop. Returns a `Pin<Box<Future>>` because rust.
fn dispatch_loop<'a>(
&'a self,
index: usize,
ctx: Context,
msg: FullEvent,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if index == 0 {
self.frameworks[index].dispatch(ctx, msg).await
} else {
self.frameworks[index]
.dispatch(ctx.clone(), msg.clone())
.await;
self.dispatch_loop(index - 1, ctx, msg).await
}
})
}
}

View file

@ -13,6 +13,10 @@ use serenity::{
use youmubot_prelude::announcer::AnnouncerHandler;
use youmubot_prelude::*;
use crate::compose_framework::ComposedFramework;
mod compose_framework;
struct Handler {
hooks: Vec<RwLock<Box<dyn Hook>>>,
ready_hooks: Vec<fn(&Context) -> CommandResult>,
@ -172,7 +176,7 @@ async fn main() {
}
};
data.insert::<Env>(env);
data.insert::<Env>(env.clone());
#[cfg(feature = "core")]
println!("Core enabled.");
@ -184,6 +188,41 @@ async fn main() {
// Set up base framework
let fw = setup_framework(&token[..]).await;
// Poise for application commands
let poise_fw = poise::Framework::builder()
.setup(|_, _, _| Box::pin(async { Ok(env) as Result<_> }))
.options(poise::FrameworkOptions {
prefix_options: poise::PrefixFrameworkOptions {
prefix: None,
mention_as_prefix: true,
execute_untracked_edits: true,
execute_self_messages: false,
ignore_thread_creation: true,
case_insensitive_commands: true,
..Default::default()
},
on_error: |err| {
Box::pin(async move {
if let poise::FrameworkError::Command { error, ctx, .. } = err {
let reply = format!(
"Command '{}' returned error {:?}",
ctx.invoked_command_name(),
error
);
ctx.reply(&reply).await.pls_ok();
println!("{}", reply)
} else {
eprintln!("Poise error: {:?}", err)
}
})
},
commands: vec![poise_register()],
..Default::default()
})
.build();
let composed = ComposedFramework::new(vec![Box::new(fw), Box::new(poise_fw)]);
// Sets up a client
let mut client = {
// Attempt to connect and set up a framework
@ -198,7 +237,7 @@ async fn main() {
| GatewayIntents::DIRECT_MESSAGE_REACTIONS;
Client::builder(token, intents)
.type_map(data)
.framework(fw)
.framework(composed)
.event_handler(handler)
.await
.unwrap()
@ -276,6 +315,18 @@ async fn setup_framework(token: &str) -> StandardFramework {
fw
}
// Poise command to register
#[poise::command(
prefix_command,
rename = "register",
required_permissions = "MANAGE_GUILD"
)]
async fn poise_register(ctx: CmdContext<'_, Env>) -> Result<()> {
// TODO: make this work for guild owners too
poise::builtins::register_application_commands_buttons(ctx).await?;
Ok(())
}
// Hooks!
#[hook]