Finish refactoring

This commit is contained in:
2025-08-10 21:15:53 -03:00
parent f123282699
commit a004fe2308
50 changed files with 2639 additions and 587 deletions
+330
View File
@@ -0,0 +1,330 @@
use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait;
use thiserror::Error;
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt};
use twilight_http::Client;
use crate::{
framework::{
commands::{Command, runner::CommandRunner},
events::traits::{
OnInteractionHandler, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
OnReadyHandler, OnReconnectHandler, OnServerCreateHandler, OnServerDeleteHandler,
OnServerUpdateHandler,
},
interactions::{components::Component, registry::ComponentRegistry},
runtime::Runtime,
},
utils::Bye,
};
#[derive(Debug, Error)]
pub enum RuntimeError {
#[error("The application's details could not be fetched")]
ApplicationFetchingFailed,
}
#[async_trait]
pub trait IntoPrefixes<T> {
async fn get(
&self,
runtime: &Runtime<T>,
server_id: Option<u64>,
user_id: Option<u64>,
) -> Vec<String>;
}
#[async_trait]
impl<T> IntoPrefixes<T> for String {
async fn get(
&self,
_: &Runtime<T>,
_: Option<u64>,
_: Option<u64>,
) -> Vec<String> {
vec![self.to_string()]
}
}
#[async_trait]
impl<T> IntoPrefixes<T> for &str {
async fn get(
&self,
_: &Runtime<T>,
_: Option<u64>,
_: Option<u64>,
) -> Vec<String> {
vec![self.to_string()]
}
}
#[async_trait]
impl<T: Send + Sync + 'static, const N: usize, S: Send + Sync + 'static> IntoPrefixes<S> for [T; N]
where
T: ToString,
{
async fn get(
&self,
_: &Runtime<S>,
_: Option<u64>,
_: Option<u64>,
) -> Vec<String> {
self.iter().map(|s| s.to_string()).collect()
}
}
#[async_trait]
impl<F, Fut, T> IntoPrefixes<T> for F
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Vec<String>> + Send,
{
async fn get(
&self,
_: &Runtime<T>,
_: Option<u64>,
_: Option<u64>,
) -> Vec<String> {
(self)().await
}
}
pub struct Bot<T: Send + Sync = ()> {
state: Arc<T>,
prefixes: Arc<dyn IntoPrefixes<T> + Send + Sync>,
intents: Intents,
commands: Vec<Arc<dyn Command<T>>>,
components: Vec<Arc<dyn Component<T>>>,
on_ready_handlers: Vec<Arc<dyn OnReadyHandler<T>>>,
on_reconnect_handlers: Vec<Arc<dyn OnReconnectHandler<T>>>,
on_interaction_handlers: Vec<Arc<dyn OnInteractionHandler<T>>>,
on_message_handlers: Vec<Arc<dyn OnMessageHandler<T>>>,
on_message_edit_handlers: Vec<Arc<dyn OnMessageEditHandler<T>>>,
on_message_delete_handlers: Vec<Arc<dyn OnMessageDeleteHandler<T>>>,
on_server_create_handlers: Vec<Arc<dyn OnServerCreateHandler<T>>>,
on_server_update_handlers: Vec<Arc<dyn OnServerUpdateHandler<T>>>,
on_server_delete_handlers: Vec<Arc<dyn OnServerDeleteHandler<T>>>,
}
impl<T: 'static + Send + Sync> Bot<T> {
pub fn new(state: T, prefixes: impl IntoPrefixes<T> + Send + Sync + 'static) -> Self {
Self {
state: Arc::new(state),
prefixes: Arc::new(prefixes),
intents: Intents::empty(),
commands: Vec::new(),
components: Vec::new(),
on_ready_handlers: Vec::new(),
on_reconnect_handlers: Vec::new(),
on_interaction_handlers: Vec::new(),
on_message_handlers: vec![Arc::new(CommandRunner)],
on_message_edit_handlers: Vec::new(),
on_message_delete_handlers: Vec::new(),
on_server_create_handlers: Vec::new(),
on_server_update_handlers: Vec::new(),
on_server_delete_handlers: Vec::new(),
}
}
pub fn intent(mut self, intent: Intents) -> Self {
self.intents.insert(intent);
self
}
pub fn command(mut self, command: impl Command<T> + 'static) -> Self {
self.commands.push(Arc::new(command));
self
}
pub fn component(mut self, component: impl Component<T> + 'static) -> Self {
self.components.push(Arc::new(component));
self
}
pub fn on_ready(mut self, handler: impl OnReadyHandler<T> + 'static) -> Self {
self.on_ready_handlers.push(Arc::new(handler));
self
}
pub fn on_reconnect(mut self, handler: impl OnReconnectHandler<T> + 'static) -> Self {
self.on_reconnect_handlers.push(Arc::new(handler));
self
}
pub fn on_interaction(mut self, handler: impl OnInteractionHandler<T> + 'static) -> Self {
self.on_interaction_handlers.push(Arc::new(handler));
self
}
pub fn on_message(mut self, handler: impl OnMessageHandler<T> + 'static) -> Self {
self.on_message_handlers.push(Arc::new(handler));
self
}
pub fn on_message_edit(mut self, handler: impl OnMessageEditHandler<T> + 'static) -> Self {
self.on_message_edit_handlers.push(Arc::new(handler));
self
}
pub fn on_message_delete(mut self, handler: impl OnMessageDeleteHandler<T> + 'static) -> Self {
self.on_message_delete_handlers.push(Arc::new(handler));
self
}
pub fn on_server_create(mut self, handler: impl OnServerCreateHandler<T> + 'static) -> Self {
self.on_server_create_handlers.push(Arc::new(handler));
self
}
pub fn on_server_update(mut self, handler: impl OnServerUpdateHandler<T> + 'static) -> Self {
self.on_server_update_handlers.push(Arc::new(handler));
self
}
pub fn on_server_delete(mut self, handler: impl OnServerDeleteHandler<T> + 'static) -> Self {
self.on_server_delete_handlers.push(Arc::new(handler));
self
}
pub async fn run(self, token: &str) -> Result<(), RuntimeError> {
let http = Client::new(token.to_string());
let app = http
.current_user_application()
.await
.inspect_err(|e| log::error!("Could not fetch application data! {e:?}"))
.map_err(|_| RuntimeError::ApplicationFetchingFailed)?
.model()
.await
.inspect_err(|e| log::error!("Could not parse application data! {e:?}"))
.map_err(|_| RuntimeError::ApplicationFetchingFailed)?;
let registry = ComponentRegistry::new();
for component in &self.components {
let component_id = component.custom_id();
registry.register(
component_id.expect("The component registered by default must have a custom ID"),
component.clone(),
);
}
let bot = Arc::new(self);
let runtime = Arc::new(Runtime {
http,
app,
registry,
state: bot.state.clone(),
prefixes: bot.prefixes.clone(),
commands: bot.commands.clone(),
});
let mut gateway = Shard::new(ShardId::new(0, 1), token.to_string(), bot.intents);
loop {
if let Some(Ok(event)) = gateway.next_event(EventTypeFlags::all()).await {
let task_rt = runtime.clone();
let task_bot = bot.clone();
tokio::spawn(async move {
match event {
Event::Ready(event) => {
for handler in &task_bot.on_ready_handlers {
handler
.on_ready(&event, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on ready for {handler:?}! {e:?}")
)
.bye();
}
}
Event::GatewayReconnect => {
for handler in &task_bot.on_reconnect_handlers {
handler
.on_reconnect(task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on reconnect for {handler:?}! {e:?}")
)
.bye();
}
}
Event::InteractionCreate(interaction) => {
for handler in &task_bot.on_interaction_handlers {
handler
.on_interaction(&interaction, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on interaction for {handler:?}! {e:?}")
)
.bye();
}
}
Event::MessageCreate(message) => {
for handler in &task_bot.on_message_handlers {
log::trace!("Running {handler:?}");
handler
.on_message(&message, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on message for {handler:?}! {e:?}")
)
.bye();
}
}
Event::MessageUpdate(message) => {
for handler in &task_bot.on_message_edit_handlers {
handler
.on_message_edit(&message, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on message edit for {handler:?}! {e:?}")
)
.bye();
}
}
Event::MessageDelete(message) => {
for handler in &task_bot.on_message_delete_handlers {
handler
.on_message_delete(&message, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on message delete for {handler:?}! {e:?}")
)
.bye();
}
}
Event::GuildCreate(guild) => {
for handler in &task_bot.on_server_create_handlers {
handler
.on_server_create(&guild, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on server create for {handler:?}! {e:?}")
)
.bye();
}
}
Event::GuildDelete(guild) => {
for handler in &task_bot.on_server_delete_handlers {
handler
.on_server_delete(&guild, task_rt.clone())
.await
.inspect_err(
|e| log::error!("An error occurred while handling on server delete for {handler:?}! {e:?}")
)
.bye();
}
}
_ => {
log::trace!("Unhandled event {:?} received", event.kind());
}
}
});
}
}
}
}
+5 -2
View File
@@ -1,4 +1,7 @@
use std::{fmt, ops::{Deref, DerefMut}};
use std::{
fmt,
ops::{Deref, DerefMut},
};
use thiserror::Error;
@@ -216,7 +219,7 @@ impl fmt::Display for Argument {
} else {
argument.push('>');
}
write!(f, "{argument}")
}
}
+13 -19
View File
@@ -1,30 +1,24 @@
use std::sync::Arc;
use twilight_http::Client;
use twilight_model::{gateway::payload::incoming::MessageCreate, oauth::Application};
use twilight_model::gateway::payload::incoming::MessageCreate;
use crate::framework::{commands::messages::MessageSender, handlers::Handlers, interactions::registry::Registry};
use crate::framework::{commands::messages::MessageSender, runtime::Runtime};
pub struct CommandContext<'a> {
pub struct CommandContext<'a, T> {
pub event: &'a MessageCreate,
pub handlers: Arc<Handlers>
pub rt: Arc<Runtime<T>>,
pub prefix: &'a str,
}
impl<'a> CommandContext<'a> {
pub fn new(
event: &'a MessageCreate,
handlers: Arc<Handlers>,
) -> Self {
Self {
event,
handlers
}
impl<'a, T> CommandContext<'a, T>
where
T: Send + Sync + 'static,
{
pub fn new(event: &'a MessageCreate, rt: Arc<Runtime<T>>, prefix: &'a str) -> Self {
Self { event, rt, prefix }
}
pub fn send(&self) -> MessageSender {
MessageSender::new(
self.event.channel_id,
self.handlers.clone()
)
pub fn send(&self) -> MessageSender<T> {
MessageSender::new(self.event.channel_id, self.rt.clone())
}
}
+30 -28
View File
@@ -19,27 +19,30 @@ use twilight_model::{
use twilight_util::builder::InteractionResponseDataBuilder;
use crate::framework::{
handlers::Handlers,
interactions::components::{ActionRow, Component},
runtime::Runtime,
};
pub struct MessageSender {
pub struct MessageSender<T> {
channel_id: Id<ChannelMarker>,
handlers: Arc<Handlers>,
rt: Arc<Runtime<T>>,
content: Option<String>,
embeds: Vec<Embed>,
// Row index, component
components: Vec<(u8, Arc<dyn Component>)>,
components: Vec<(u8, Arc<dyn Component<T>>)>,
}
impl MessageSender {
pub fn new(channel_id: Id<ChannelMarker>, handlers: Arc<Handlers>) -> Self {
impl<T> MessageSender<T>
where
T: Send + Sync + 'static,
{
pub fn new(channel_id: Id<ChannelMarker>, rt: Arc<Runtime<T>>) -> Self {
Self {
channel_id,
handlers,
rt,
content: None,
components: vec![],
embeds: vec![],
@@ -58,14 +61,14 @@ impl MessageSender {
self
}
pub fn component(mut self, component: impl Component + 'static, row: u8) -> Self {
pub fn component(mut self, component: impl Component<T> + 'static, row: u8) -> Self {
self.components.push((row, Arc::new(component)));
self
}
pub async fn send(self) -> Result<Response<Message>, Error> {
let mut builder = self.handlers.http.create_message(self.channel_id);
let mut builder = self.rt.http.create_message(self.channel_id);
if let Some(content) = &self.content {
builder = builder.content(content);
@@ -92,9 +95,7 @@ impl MessageSender {
if let Some(custom_id) = &custom_id {
custom_ids.push(custom_id.clone());
self.handlers
.registry
.register(custom_id, component.clone());
self.rt.registry.register(custom_id, component.clone());
}
current_row.add(custom_id.clone(), component.clone());
@@ -111,7 +112,7 @@ impl MessageSender {
if result.is_err() {
for custom_id in custom_ids {
self.handlers.registry.unregister(custom_id);
self.rt.registry.unregister(custom_id);
}
}
@@ -119,7 +120,7 @@ impl MessageSender {
}
}
impl IntoFuture for MessageSender {
impl<T: Send + Sync + 'static> IntoFuture for MessageSender<T> {
type Output = Result<Response<Message>, Error>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
@@ -128,29 +129,32 @@ impl IntoFuture for MessageSender {
}
}
pub struct OriginalMessageUpdater {
pub struct OriginalMessageUpdater<T> {
interaction_id: Id<InteractionMarker>,
interaction_token: String,
handlers: Arc<Handlers>,
rt: Arc<Runtime<T>>,
content: Option<String>,
embeds: Vec<Embed>,
// Row index, component
components: Vec<(u8, Arc<dyn Component>)>,
components: Vec<(u8, Arc<dyn Component<T>>)>,
}
impl OriginalMessageUpdater {
impl<T> OriginalMessageUpdater<T>
where
T: Send + Sync + 'static,
{
pub fn new(
interaction_id: Id<InteractionMarker>,
interaction_token: String,
handlers: Arc<Handlers>,
rt: Arc<Runtime<T>>,
) -> Self {
Self {
interaction_id,
interaction_token,
handlers,
rt,
content: None,
components: vec![],
embeds: vec![],
@@ -169,7 +173,7 @@ impl OriginalMessageUpdater {
self
}
pub fn component(mut self, component: impl Component + 'static, row: u8) -> Self {
pub fn component(mut self, component: impl Component<T> + 'static, row: u8) -> Self {
self.components.push((row, Arc::new(component)));
self
@@ -203,9 +207,7 @@ impl OriginalMessageUpdater {
if let Some(custom_id) = &custom_id {
custom_ids.push(custom_id.clone());
self.handlers
.registry
.register(custom_id, component.clone());
self.rt.registry.register(custom_id, component.clone());
}
current_row.add(custom_id.clone(), component.clone());
@@ -219,9 +221,9 @@ impl OriginalMessageUpdater {
builder = builder.embeds(self.embeds);
let result = self
.handlers
.rt
.http
.interaction(self.handlers.app.id)
.interaction(self.rt.app.id)
.create_response(
self.interaction_id,
&self.interaction_token,
@@ -234,7 +236,7 @@ impl OriginalMessageUpdater {
if result.is_err() {
for custom_id in custom_ids {
self.handlers.registry.unregister(custom_id);
self.rt.registry.unregister(custom_id);
}
}
@@ -242,7 +244,7 @@ impl OriginalMessageUpdater {
}
}
impl IntoFuture for OriginalMessageUpdater {
impl<T: Send + Sync + 'static> IntoFuture for OriginalMessageUpdater<T> {
type Output = Result<Response<EmptyBody>, Error>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
+11 -2
View File
@@ -1,3 +1,5 @@
use std::fmt::Debug;
use async_trait::async_trait;
use thiserror::Error;
@@ -6,15 +8,22 @@ use crate::framework::commands::{arguments::Argument, context::CommandContext};
pub mod arguments;
pub mod context;
pub mod messages;
pub mod runner;
#[derive(Debug, Error)]
pub enum CommandError {
#[error("A message was attempted to be sent but it failed")]
MessageSendingFailed,
#[error("A database query failed")]
DatabaseQueryFailed,
#[error("A database operation failed due to an inconsistent state")]
DatabaseInconsistentState,
}
#[async_trait]
pub trait Command: Send + Sync {
pub trait Command<T>: Send + Sync + Debug {
fn name(&self) -> &'static str;
fn aliases(&self) -> &'static [&'static str] {
&[]
@@ -36,7 +45,7 @@ pub trait Command: Send + Sync {
all
}
async fn execute(&self, ctx: &CommandContext, args: &str) -> Result<(), CommandError>;
async fn execute(&self, ctx: CommandContext<'_, T>, args: &str) -> Result<(), CommandError>;
}
pub struct CommandParts<'a> {
+45
View File
@@ -0,0 +1,45 @@
use std::sync::Arc;
use async_trait::async_trait;
use twilight_model::gateway::payload::incoming::MessageCreate;
use crate::framework::{
commands::{context::CommandContext, get_command_parts},
events::traits::{EventResult, OnMessageHandler},
runtime::Runtime,
};
#[derive(Debug)]
pub struct CommandRunner;
#[async_trait]
impl<T: Send + Sync + 'static> OnMessageHandler<T> for CommandRunner {
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<T>>) -> EventResult {
if event.author.bot || event.author.system.unwrap_or(false) {
return Ok(());
}
let prefixes = rt
.get_prefixes(event.guild_id.map(|i| i.get()), Some(event.author.id.get()))
.await;
for prefix in prefixes {
if let Some(parts) = get_command_parts(&prefix, &event.content) {
let command = rt.commands.iter().find(|cmd| {
cmd.identifiers()
.contains(&parts.name.to_ascii_lowercase().as_str())
});
if let Some(command) = command {
command
.execute(CommandContext::new(event, rt.clone(), &prefix), parts.args)
.await?;
break;
}
}
}
Ok(())
}
}
+1
View File
@@ -0,0 +1 @@
pub mod traits;
+83
View File
@@ -0,0 +1,83 @@
use std::{error::Error, fmt::Debug, sync::Arc};
use async_trait::async_trait;
use twilight_model::gateway::payload::incoming::{
GuildCreate, GuildDelete, GuildUpdate, InteractionCreate, MessageCreate, MessageDelete,
MessageUpdate, Ready,
};
use crate::framework::runtime::Runtime;
pub type EventResult = Result<(), Box<dyn Error>>;
#[async_trait]
pub trait OnReadyHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_ready(&self, event: &Ready, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnReconnectHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_reconnect(&self, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnInteractionHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_interaction(&self, event: &InteractionCreate, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnMessageHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnMessageEditHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnMessageDeleteHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_message_delete(&self, event: &MessageDelete, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnServerCreateHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_server_create(&self, event: &GuildCreate, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnServerUpdateHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_server_update(&self, event: &GuildUpdate, rt: Arc<Runtime<T>>) -> EventResult;
}
#[async_trait]
pub trait OnServerDeleteHandler<T>: Send + Sync + Debug
where
T: Send + Sync,
{
async fn on_server_delete(&self, event: &GuildDelete, rt: Arc<Runtime<T>>) -> EventResult;
}
-12
View File
@@ -1,12 +0,0 @@
use scylla::client::session::Session;
use twilight_http::Client;
use twilight_model::oauth::Application;
use crate::framework::interactions::registry::Registry;
pub struct Handlers {
pub http: Client,
pub app: Application,
pub db: Session,
pub registry: Registry,
}
+19 -13
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{fmt::Debug, sync::Arc};
use async_trait::async_trait;
use twilight_model::channel::message::{
@@ -8,18 +8,20 @@ use twilight_model::channel::message::{
use crate::framework::interactions::{InteractionError, context::InteractionContext};
pub struct ActionRow {
components: Vec<(Option<String>, Arc<dyn Component>)>,
type ComponentWithID<T> = (Option<String>, Arc<dyn Component<T>>);
pub struct ActionRow<T> {
components: Vec<ComponentWithID<T>>,
}
impl ActionRow {
pub fn new(components: &[(Option<String>, Arc<dyn Component>)]) -> Self {
impl<T> ActionRow<T> {
pub fn new(components: &[ComponentWithID<T>]) -> Self {
Self {
components: components.to_vec(),
}
}
pub fn add(&mut self, id: Option<String>, component: Arc<dyn Component>) {
pub fn add(&mut self, id: Option<String>, component: Arc<dyn Component<T>>) {
self.components.push((id, component));
}
@@ -27,6 +29,10 @@ impl ActionRow {
self.components.len()
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn build(self) -> TwilightComponent {
TwilightComponent::ActionRow(TwilightActionRow {
components: self
@@ -39,19 +45,19 @@ impl ActionRow {
}
#[async_trait]
pub trait Component: Send + Sync {
pub trait Component<T>: Send + Sync + Debug {
fn custom_id(&self) -> Option<String> {
Some(String::from("component"))
}
fn build(&self, id: Option<String>) -> TwilightComponent;
async fn execute(&self, ctx: InteractionContext) -> Result<(), InteractionError>;
async fn execute(&self, ctx: InteractionContext<T>) -> Result<(), InteractionError>;
}
#[async_trait]
impl<T> Component for T
impl<T, S: Send + Sync + 'static> Component<S> for T
where
T: Button,
T: Button<S>,
{
fn custom_id(&self) -> Option<String> {
Button::id(self)
@@ -73,13 +79,13 @@ where
})
}
async fn execute(&self, ctx: InteractionContext) -> Result<(), InteractionError> {
async fn execute(&self, ctx: InteractionContext<S>) -> Result<(), InteractionError> {
self.on_press(ctx).await
}
}
#[async_trait]
pub trait Button: Send + Sync {
pub trait Button<S: Send + Sync + 'static>: Send + Sync + Debug {
fn id(&self) -> Option<String> {
if self.url().is_none() {
Some(String::from("Button"))
@@ -97,7 +103,7 @@ pub trait Button: Send + Sync {
None
}
async fn on_press(&self, ctx: InteractionContext) -> Result<(), InteractionError> {
async fn on_press(&self, ctx: InteractionContext<S>) -> Result<(), InteractionError> {
ctx.defer().await
}
}
+23 -25
View File
@@ -1,36 +1,30 @@
use std::sync::Arc;
use twilight_http::Client;
use twilight_model::{
gateway::payload::incoming::InteractionCreate,
http::interaction::{InteractionResponse, InteractionResponseType},
id::{Id, marker::InteractionMarker},
oauth::Application,
};
use crate::framework::{
commands::messages::OriginalMessageUpdater, handlers::Handlers, interactions::{registry::Registry, InteractionError}
commands::messages::OriginalMessageUpdater, interactions::InteractionError, runtime::Runtime,
};
pub struct InteractionContext {
pub struct InteractionContext<T> {
pub event: InteractionCreate,
pub handlers: Arc<Handlers>,
pub rt: Arc<Runtime<T>>,
pub prefix: String,
}
impl InteractionContext {
pub fn new(
event: InteractionCreate,
handlers: Arc<Handlers>,
) -> Self {
Self {
event,
handlers
}
impl<T> InteractionContext<T> {
pub fn new(event: InteractionCreate, rt: Arc<Runtime<T>>, prefix: String) -> Self {
Self { event, rt, prefix }
}
pub async fn defer(&self) -> Result<(), InteractionError> {
self.handlers.http
.interaction(self.handlers.app.id)
self.rt
.http
.interaction(self.rt.app.id)
.create_response(
self.event.id,
&self.event.token,
@@ -46,8 +40,9 @@ impl InteractionContext {
}
pub async fn think(&self) -> Result<(), InteractionError> {
self.handlers.http
.interaction(self.handlers.app.id)
self.rt
.http
.interaction(self.rt.app.id)
.create_response(
self.event.id,
&self.event.token,
@@ -62,27 +57,30 @@ impl InteractionContext {
Ok(())
}
pub fn original_message(&self) -> Option<OriginalMessage> {
pub fn original_message(&self) -> Option<OriginalMessage<T>> {
self.event.message.as_ref().map(|_| OriginalMessage {
interaction_id: self.event.id,
interaction_token: self.event.token.clone(),
handlers: self.handlers.clone()
rt: self.rt.clone(),
})
}
}
pub struct OriginalMessage {
pub struct OriginalMessage<T> {
interaction_id: Id<InteractionMarker>,
interaction_token: String,
handlers: Arc<Handlers>
rt: Arc<Runtime<T>>,
}
impl OriginalMessage {
pub fn edit(&self) -> OriginalMessageUpdater {
impl<T> OriginalMessage<T>
where
T: Send + Sync + 'static,
{
pub fn edit(&self) -> OriginalMessageUpdater<T> {
OriginalMessageUpdater::new(
self.interaction_id,
self.interaction_token.clone(),
self.handlers.clone(),
self.rt.clone(),
)
}
}
+4
View File
@@ -3,6 +3,7 @@ use thiserror::Error;
pub mod components;
pub mod context;
pub mod registry;
pub mod runner;
#[derive(Debug, Error)]
pub enum InteractionError {
@@ -19,4 +20,7 @@ pub enum InteractionError {
#[error("The original message was attempted to be edited, but it failed")]
OriginalMessageEditingFailed,
#[error("No prefixes were returned for the event")]
PrefixesVectorEmpty,
}
+30 -10
View File
@@ -1,28 +1,34 @@
use std::sync::Arc;
use scc::HashMap;
use twilight_http::Client;
use twilight_model::{
application::interaction::InteractionData, gateway::payload::incoming::InteractionCreate,
oauth::Application,
};
use crate::framework::{handlers::Handlers, interactions::{context::InteractionContext, InteractionError}};
use crate::framework::{
interactions::{InteractionError, context::InteractionContext},
runtime::Runtime,
};
use super::components::Component;
pub struct Registry {
inner: HashMap<String, Arc<dyn Component>>,
#[derive(Default)]
pub struct ComponentRegistry<T> {
inner: HashMap<String, Arc<dyn Component<T>>>,
}
impl Registry {
impl<T> ComponentRegistry<T>
where
T: Send + Sync + 'static,
{
pub fn new() -> Self {
Self {
inner: Default::default(),
}
}
pub fn register(&self, id: impl ToString, component: Arc<dyn Component>) {
pub fn register(&self, id: impl ToString, component: Arc<dyn Component<T>>) {
self.inner.upsert(id.to_string(), component);
}
@@ -32,15 +38,29 @@ impl Registry {
pub async fn execute(
&self,
event: InteractionCreate,
handlers: Arc<Handlers>,
event: &InteractionCreate,
rt: Arc<Runtime<T>>,
) -> Result<(), InteractionError> {
if let Some(data) = &event.data {
match data {
InteractionData::MessageComponent(data) => {
if let Some(component) = self.inner.get_async(&data.custom_id).await {
let prefixes = rt
.get_prefixes(
event.guild_id.map(|id| id.get()),
event.author_id().map(|id| id.get()),
)
.await;
let prefix = prefixes
.first()
.ok_or(InteractionError::PrefixesVectorEmpty)?;
component
.execute(InteractionContext::new(event, handlers))
.execute(InteractionContext::new(
event.clone(),
rt.clone(),
prefix.to_string(),
))
.await
} else {
Err(InteractionError::InteractionNotFound)
+26
View File
@@ -0,0 +1,26 @@
use std::sync::Arc;
use async_trait::async_trait;
use twilight_model::gateway::payload::incoming::InteractionCreate;
use crate::{
framework::{
events::traits::{EventResult, OnInteractionHandler},
runtime::Runtime,
},
state::State,
};
#[derive(Debug)]
pub struct InteractionRunner;
#[async_trait]
impl OnInteractionHandler<State> for InteractionRunner {
async fn on_interaction(
&self,
event: &InteractionCreate,
rt: Arc<Runtime<State>>,
) -> EventResult {
Ok(rt.registry.execute(event, rt.clone()).await?)
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
pub mod bot;
pub mod commands;
pub mod events;
pub mod interactions;
pub mod utils;
pub mod handlers;
pub mod runtime;
+33
View File
@@ -0,0 +1,33 @@
use std::sync::Arc;
use twilight_http::Client;
use twilight_model::oauth::Application;
use crate::framework::{
bot::IntoPrefixes, commands::Command, interactions::registry::ComponentRegistry,
};
pub struct Runtime<T> {
pub http: Client,
pub app: Application,
pub registry: ComponentRegistry<T>,
pub prefixes: Arc<dyn IntoPrefixes<T> + Send + Sync + 'static>,
pub commands: Vec<Arc<dyn Command<T>>>,
pub state: Arc<T>,
}
impl<T> Runtime<T> {
pub async fn get_prefixes(&self, server_id: Option<u64>, user_id: Option<u64>) -> Vec<String> {
self.prefixes.clone().get(self, server_id, user_id).await
}
pub fn get_command(&self, name: &str) -> Option<Arc<dyn Command<T>>> {
self.commands
.iter()
.find(|cmd| {
cmd.identifiers()
.contains(&name.to_ascii_lowercase().as_str())
})
.cloned()
}
}
-33
View File
@@ -1,33 +0,0 @@
pub trait EscapeDiscordMarkdown {
fn escape(self) -> String;
}
impl<T> EscapeDiscordMarkdown for T
where
T: ToString,
{
fn escape(self) -> String {
let input = self.to_string();
let mut escaped = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
'\\' | '*' | '_' | '~' | '`' | '>' | '|' | '[' | ']' | '(' | ')' | '#' | '+'
| '-' | '=' | '.' | '!' => {
escaped.push('\\');
escaped.push(ch);
}
_ => escaped.push(ch),
}
}
// Now block ping attempts — break mentions
escaped
.replace("@everyone", "@\u{200B}everyone")
.replace("@here", "@\u{200B}here")
.replace("<@", "<@\u{200B}") // user mentions
.replace("<@&", "<@&\u{200B}") // role mentions
.replace("<#", "<#\u{200B}") // channel mentions
}
}