Finish refactoring
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod traits;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user