Add basic moderator commands

This commit is contained in:
2025-08-23 00:02:10 -03:00
parent 8868b18be3
commit eaa38f3474
27 changed files with 752 additions and 249 deletions
+10 -3
View File
@@ -64,7 +64,7 @@ where
fn build(&self, id: Option<String>) -> TwilightComponent {
TwilightComponent::Button(TwilightButton {
custom_id: id,
disabled: false,
disabled: self.disabled(),
emoji: None,
label: self.label().map(|i| i.to_string()),
style: if self.url().is_none() {
@@ -95,16 +95,23 @@ where
None
}
}
fn style(&self) -> ButtonStyle {
ButtonStyle::Secondary
}
fn label(&self) -> Option<&'static str> {
fn label(&self) -> Option<String> {
None
}
fn url(&self) -> Option<&'static str> {
fn url(&self) -> Option<String> {
None
}
fn disabled(&self) -> bool {
false
}
async fn on_press(&self, ctx: InteractionContext<S, E>) -> Result<(), InteractionError> {
ctx.defer().await
}
+86 -31
View File
@@ -1,13 +1,16 @@
use std::sync::Arc;
use twilight_http::{Error, Response, response::marker::EmptyBody};
use twilight_model::{
gateway::payload::incoming::InteractionCreate,
http::interaction::{InteractionResponse, InteractionResponseType},
id::{Id, marker::InteractionMarker},
};
use twilight_util::builder::InteractionResponseDataBuilder;
use crate::framework::{
commands::messages::OriginalMessageUpdater, interactions::InteractionError, runtime::Runtime,
interactions::{InteractionError, components::ActionRow},
messages::MessageEditBuilder,
runtime::Runtime,
};
pub struct InteractionContext<T, E> {
@@ -17,7 +20,11 @@ pub struct InteractionContext<T, E> {
pub extras: E,
}
impl<T, E> InteractionContext<T, E> {
impl<T, E> InteractionContext<T, E>
where
T: Send + Sync + 'static,
E: Send + Sync + 'static,
{
pub fn new(
event: InteractionCreate,
rt: Arc<Runtime<T, E>>,
@@ -44,37 +51,85 @@ impl<T, E> InteractionContext<T, E> {
kind: InteractionResponseType::DeferredUpdateMessage,
},
)
.await
.map_err(|_| InteractionError::DeferringFailed)?;
.await?;
Ok(())
}
pub fn original_message(&self) -> Option<OriginalMessage<T, E>> {
self.event.message.as_ref().map(|_| OriginalMessage {
interaction_id: self.event.id,
interaction_token: self.event.token.clone(),
rt: self.rt.clone(),
})
}
}
pub struct OriginalMessage<T, E> {
interaction_id: Id<InteractionMarker>,
interaction_token: String,
rt: Arc<Runtime<T, E>>,
}
impl<T, E> OriginalMessage<T, E>
where
T: Send + Sync + 'static,
E: Send + Sync + 'static,
{
pub fn edit(&self) -> OriginalMessageUpdater<T, E> {
OriginalMessageUpdater::new(
self.interaction_id,
self.interaction_token.clone(),
self.rt.clone(),
)
pub async fn edit_original_message(
&self,
builder: MessageEditBuilder<T, E>,
) -> Result<Response<EmptyBody>, Error> {
let mut sender = InteractionResponseDataBuilder::new();
if let Some(content) = &builder.content {
if let Some(content) = content {
sender = sender.content(content);
} else {
sender = sender.content("");
}
}
let mut current_row = ActionRow::new(&[]);
let mut current_index = 0;
let mut custom_ids: Vec<String> = vec![];
if let Some(components) = builder.components {
let mut rows = vec![];
for (component, index) in components {
if index != current_index {
if !current_row.is_empty() {
rows.push(current_row.build());
current_row = ActionRow::new(&[]);
}
current_index += 1;
}
let custom_id = component.custom_id();
if let Some(custom_id) = &custom_id {
custom_ids.push(custom_id.clone());
self.rt.registry.register(custom_id, component.clone());
}
current_row.add(custom_id.clone(), component.clone());
}
if !current_row.is_empty() {
rows.push(current_row.build());
}
sender = sender.components(rows);
}
if let Some(embeds) = builder.embeds {
sender = sender.embeds(embeds);
}
let result = self
.rt
.http
.interaction(self.rt.app.id)
.create_response(
self.event.id,
&self.event.token,
&InteractionResponse {
data: Some(sender.build()),
kind: InteractionResponseType::UpdateMessage,
},
)
.await;
if result.is_err() {
for custom_id in custom_ids {
self.rt.registry.unregister(custom_id);
}
}
result
}
}
+8 -3
View File
@@ -1,5 +1,7 @@
use thiserror::Error;
use crate::database::models::DatabaseError;
pub mod components;
pub mod context;
pub mod registry;
@@ -12,9 +14,6 @@ pub enum InteractionError {
)]
InteractionNotFound,
#[error("The interaction was attempted to be deferred, but it failed to be done")]
DeferringFailed,
#[error("Discord responded with an interaction type Payphone does not use")]
InteractionTypeUnsent,
@@ -26,4 +25,10 @@ pub enum InteractionError {
#[error("The extras getter failed to retrieve extras")]
ExtrasGetterFailed,
#[error(transparent)]
DatabaseError(#[from] DatabaseError),
#[error(transparent)]
DiscordAPIError(#[from] twilight_http::Error),
}
+9 -2
View File
@@ -47,7 +47,10 @@ where
if let Some(data) = &event.data {
match data {
InteractionData::MessageComponent(data) => {
if let Some(component) = self.inner.get_async(&data.custom_id).await {
if let Some(guarded_component) = self.inner.get(&data.custom_id) {
let component = guarded_component.clone();
drop(guarded_component);
let prefixes = rt
.get_prefixes(
event.guild_id.map(|id| id.get()),
@@ -60,7 +63,7 @@ where
let extras = extras
.get_extra(
&event.author().expect("Component interaction had no author"),
event.author().expect("Component interaction had no author"),
&rt,
)
.await
@@ -77,6 +80,10 @@ where
extras,
))
.await
.inspect(|_| log::debug!("Executed component interaction"))
.inspect_err(|e| {
log::error!("Interaction execution failed: {e:?}")
})
} else {
Err(InteractionError::InteractionNotFound)
}