Initialize Twilight project, set up basic framework, initialize DB module
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
use std::{fmt, ops::{Deref, DerefMut}};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ArgumentParsingError {
|
||||
#[error("This argument was missing in the arguments array")]
|
||||
MissingArgument,
|
||||
|
||||
#[error("An argument was there but it could not be parsed into the specified value")]
|
||||
InvalidArgument,
|
||||
}
|
||||
|
||||
pub trait FromArguments: Sized {
|
||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError>;
|
||||
}
|
||||
|
||||
impl FromArguments for String {
|
||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
||||
if !args.is_empty() {
|
||||
match args.split_once(" ") {
|
||||
Some((arg, rest)) => Ok((arg.to_string(), rest)),
|
||||
None => Ok((args.to_string(), "")),
|
||||
}
|
||||
} else {
|
||||
Ok((args.to_string(), ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GreedyString(pub String);
|
||||
|
||||
impl FromArguments for GreedyString {
|
||||
fn from_args<'a>(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
||||
let value = GreedyString(args.to_string());
|
||||
|
||||
Ok((value, ""))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for GreedyString {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for GreedyString {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GreedyString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NeedyString(pub String);
|
||||
|
||||
impl Deref for NeedyString {
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NeedyString {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NeedyString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArguments for NeedyString {
|
||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
||||
if !args.is_empty() {
|
||||
match args.split_once(" ") {
|
||||
Some((arg, rest)) => Ok((Self(arg.to_string()), rest)),
|
||||
None => Ok((Self(args.to_string()), "")),
|
||||
}
|
||||
} else {
|
||||
Err(ArgumentParsingError::MissingArgument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArguments for bool {
|
||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
||||
let arg: &str;
|
||||
let rest: &str;
|
||||
|
||||
match args.split_once(" ") {
|
||||
Some((a, b)) => {
|
||||
arg = a;
|
||||
rest = b;
|
||||
}
|
||||
None => {
|
||||
arg = args;
|
||||
rest = "";
|
||||
}
|
||||
}
|
||||
|
||||
match arg {
|
||||
"y" | "yes" | "on" | "true" | "t" | "1" => Ok((true, rest)),
|
||||
"n" | "no" | "off" | "false" | "f" | "0" => Ok((false, rest)),
|
||||
_ => Err(ArgumentParsingError::InvalidArgument),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromArguments for Option<T>
|
||||
where
|
||||
T: FromArguments,
|
||||
{
|
||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
||||
match T::from_args(args) {
|
||||
Ok((result, rest)) => Ok((Some(result), rest)),
|
||||
Err(_) => Ok((None, args)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParseArguments: Sized {
|
||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError>;
|
||||
}
|
||||
|
||||
impl ParseArguments for () {
|
||||
fn parse(_: &str) -> Result<Self, ArgumentParsingError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> ParseArguments for A
|
||||
where
|
||||
A: FromArguments,
|
||||
{
|
||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
||||
let (a, _) = A::from_args(args)?;
|
||||
|
||||
Ok(a)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> ParseArguments for (A,)
|
||||
where
|
||||
A: FromArguments,
|
||||
{
|
||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
||||
let (a, _) = A::from_args(args)?;
|
||||
|
||||
Ok((a,))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> ParseArguments for (A, B)
|
||||
where
|
||||
A: FromArguments,
|
||||
B: FromArguments,
|
||||
{
|
||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
||||
let (a, args) = A::from_args(args)?;
|
||||
let (b, _) = B::from_args(args)?;
|
||||
|
||||
Ok((a, b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, C> ParseArguments for (A, B, C)
|
||||
where
|
||||
A: FromArguments,
|
||||
B: FromArguments,
|
||||
C: FromArguments,
|
||||
{
|
||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
||||
let (a, args) = A::from_args(args)?;
|
||||
let (b, args) = B::from_args(args)?;
|
||||
let (c, _) = C::from_args(args)?;
|
||||
|
||||
Ok((a, b, c))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Argument {
|
||||
pub name: &'static str,
|
||||
pub is_optional: bool,
|
||||
pub is_greedy: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Argument {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut argument = String::new();
|
||||
|
||||
if self.is_optional {
|
||||
argument.push('[');
|
||||
} else {
|
||||
argument.push('<');
|
||||
}
|
||||
|
||||
argument.push_str(self.name);
|
||||
|
||||
if self.is_greedy {
|
||||
argument.push_str("...");
|
||||
}
|
||||
|
||||
if self.is_optional {
|
||||
argument.push(']');
|
||||
} else {
|
||||
argument.push('>');
|
||||
}
|
||||
|
||||
write!(f, "{argument}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use twilight_http::Client;
|
||||
use twilight_model::{gateway::payload::incoming::MessageCreate, oauth::Application};
|
||||
|
||||
use crate::framework::{commands::messages::MessageSender, handlers::Handlers, interactions::registry::Registry};
|
||||
|
||||
pub struct CommandContext<'a> {
|
||||
pub event: &'a MessageCreate,
|
||||
pub handlers: Arc<Handlers>
|
||||
}
|
||||
|
||||
impl<'a> CommandContext<'a> {
|
||||
pub fn new(
|
||||
event: &'a MessageCreate,
|
||||
handlers: Arc<Handlers>,
|
||||
) -> Self {
|
||||
Self {
|
||||
event,
|
||||
handlers
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self) -> MessageSender {
|
||||
MessageSender::new(
|
||||
self.event.channel_id,
|
||||
self.handlers.clone()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
use std::{
|
||||
future::{Future, IntoFuture},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use twilight_http::{Error, Response, response::marker::EmptyBody};
|
||||
use twilight_model::{
|
||||
channel::{
|
||||
Message,
|
||||
message::{Component as TwilightComponent, Embed},
|
||||
},
|
||||
http::interaction::{InteractionResponse, InteractionResponseType},
|
||||
id::{
|
||||
Id,
|
||||
marker::{ChannelMarker, InteractionMarker},
|
||||
},
|
||||
};
|
||||
use twilight_util::builder::InteractionResponseDataBuilder;
|
||||
|
||||
use crate::framework::{
|
||||
handlers::Handlers,
|
||||
interactions::components::{ActionRow, Component},
|
||||
};
|
||||
|
||||
pub struct MessageSender {
|
||||
channel_id: Id<ChannelMarker>,
|
||||
|
||||
handlers: Arc<Handlers>,
|
||||
|
||||
content: Option<String>,
|
||||
embeds: Vec<Embed>,
|
||||
|
||||
// Row index, component
|
||||
components: Vec<(u8, Arc<dyn Component>)>,
|
||||
}
|
||||
|
||||
impl MessageSender {
|
||||
pub fn new(channel_id: Id<ChannelMarker>, handlers: Arc<Handlers>) -> Self {
|
||||
Self {
|
||||
channel_id,
|
||||
handlers,
|
||||
content: None,
|
||||
components: vec![],
|
||||
embeds: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: impl ToString) -> Self {
|
||||
self.content = Some(content.to_string());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn embed(mut self, embed: Embed) -> Self {
|
||||
self.embeds.push(embed);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn component(mut self, component: impl Component + '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);
|
||||
|
||||
if let Some(content) = &self.content {
|
||||
builder = builder.content(content);
|
||||
}
|
||||
|
||||
let mut current_row = ActionRow::new(&[]);
|
||||
let mut current_index = 0;
|
||||
|
||||
let mut rows: Vec<TwilightComponent> = vec![];
|
||||
let mut custom_ids: Vec<String> = vec![];
|
||||
|
||||
for (index, component) in self.components {
|
||||
if index != current_index {
|
||||
if current_row.len() > 0 {
|
||||
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.handlers
|
||||
.registry
|
||||
.register(custom_id, component.clone());
|
||||
}
|
||||
|
||||
current_row.add(custom_id.clone(), component.clone());
|
||||
}
|
||||
|
||||
if current_row.len() > 0 {
|
||||
rows.push(current_row.build());
|
||||
}
|
||||
|
||||
builder = builder.components(&rows);
|
||||
builder = builder.embeds(&self.embeds);
|
||||
|
||||
let result = builder.await;
|
||||
|
||||
if result.is_err() {
|
||||
for custom_id in custom_ids {
|
||||
self.handlers.registry.unregister(custom_id);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for MessageSender {
|
||||
type Output = Result<Response<Message>, Error>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(self.send())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OriginalMessageUpdater {
|
||||
interaction_id: Id<InteractionMarker>,
|
||||
interaction_token: String,
|
||||
|
||||
handlers: Arc<Handlers>,
|
||||
|
||||
content: Option<String>,
|
||||
embeds: Vec<Embed>,
|
||||
|
||||
// Row index, component
|
||||
components: Vec<(u8, Arc<dyn Component>)>,
|
||||
}
|
||||
|
||||
impl OriginalMessageUpdater {
|
||||
pub fn new(
|
||||
interaction_id: Id<InteractionMarker>,
|
||||
interaction_token: String,
|
||||
handlers: Arc<Handlers>,
|
||||
) -> Self {
|
||||
Self {
|
||||
interaction_id,
|
||||
interaction_token,
|
||||
handlers,
|
||||
content: None,
|
||||
components: vec![],
|
||||
embeds: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: impl ToString) -> Self {
|
||||
self.content = Some(content.to_string());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn embed(mut self, embed: Embed) -> Self {
|
||||
self.embeds.push(embed);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn component(mut self, component: impl Component + 'static, row: u8) -> Self {
|
||||
self.components.push((row, Arc::new(component)));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn send(self) -> Result<Response<EmptyBody>, Error> {
|
||||
let mut builder = InteractionResponseDataBuilder::new();
|
||||
|
||||
if let Some(content) = &self.content {
|
||||
builder = builder.content(content);
|
||||
}
|
||||
|
||||
let mut current_row = ActionRow::new(&[]);
|
||||
let mut current_index = 0;
|
||||
|
||||
let mut rows: Vec<TwilightComponent> = vec![];
|
||||
let mut custom_ids: Vec<String> = vec![];
|
||||
|
||||
for (index, component) in self.components {
|
||||
if index != current_index {
|
||||
if current_row.len() > 0 {
|
||||
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.handlers
|
||||
.registry
|
||||
.register(custom_id, component.clone());
|
||||
}
|
||||
|
||||
current_row.add(custom_id.clone(), component.clone());
|
||||
}
|
||||
|
||||
if current_row.len() > 0 {
|
||||
rows.push(current_row.build());
|
||||
}
|
||||
|
||||
builder = builder.components(rows);
|
||||
builder = builder.embeds(self.embeds);
|
||||
|
||||
let result = self
|
||||
.handlers
|
||||
.http
|
||||
.interaction(self.handlers.app.id)
|
||||
.create_response(
|
||||
self.interaction_id,
|
||||
&self.interaction_token,
|
||||
&InteractionResponse {
|
||||
data: Some(builder.build()),
|
||||
kind: InteractionResponseType::UpdateMessage,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
for custom_id in custom_ids {
|
||||
self.handlers.registry.unregister(custom_id);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for OriginalMessageUpdater {
|
||||
type Output = Result<Response<EmptyBody>, Error>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(self.send())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::framework::commands::{arguments::Argument, context::CommandContext};
|
||||
|
||||
pub mod arguments;
|
||||
pub mod context;
|
||||
pub mod messages;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CommandError {
|
||||
#[error("A message was attempted to be sent but it failed")]
|
||||
MessageSendingFailed,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Command: Send + Sync {
|
||||
fn name(&self) -> &'static str;
|
||||
fn aliases(&self) -> &'static [&'static str] {
|
||||
&[]
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"A Payphone command."
|
||||
}
|
||||
fn arguments(&self) -> &'static [Argument] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Returns the command name + all its aliases.
|
||||
///
|
||||
/// Returns:
|
||||
/// [`Vec<&'static str>`] - A vector with the command's name and its aliases.
|
||||
fn identifiers(&self) -> Vec<&'static str> {
|
||||
let mut all = vec![self.name()];
|
||||
all.extend_from_slice(self.aliases());
|
||||
all
|
||||
}
|
||||
|
||||
async fn execute(&self, ctx: &CommandContext, args: &str) -> Result<(), CommandError>;
|
||||
}
|
||||
|
||||
pub struct CommandParts<'a> {
|
||||
pub name: &'a str,
|
||||
pub args: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> CommandParts<'a> {
|
||||
fn new(name: &'a str, args: &'a str) -> Self {
|
||||
Self { name, args }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_command_parts<'a>(prefix: &str, message: &'a str) -> Option<CommandParts<'a>> {
|
||||
let message_trimmed_start = message.trim_ascii_start();
|
||||
|
||||
if message_trimmed_start
|
||||
.to_ascii_lowercase()
|
||||
.starts_with(&prefix.to_ascii_lowercase())
|
||||
{
|
||||
let message = message_trimmed_start.split_at(prefix.len()).1;
|
||||
|
||||
match message.split_once(" ") {
|
||||
Some((name, args)) => Some(CommandParts::new(name, args)),
|
||||
None => Some(CommandParts::new(message, "")),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use twilight_model::channel::message::{
|
||||
Component as TwilightComponent,
|
||||
component::{ActionRow as TwilightActionRow, Button as TwilightButton, ButtonStyle},
|
||||
};
|
||||
|
||||
use crate::framework::interactions::{InteractionError, context::InteractionContext};
|
||||
|
||||
pub struct ActionRow {
|
||||
components: Vec<(Option<String>, Arc<dyn Component>)>,
|
||||
}
|
||||
|
||||
impl ActionRow {
|
||||
pub fn new(components: &[(Option<String>, Arc<dyn Component>)]) -> Self {
|
||||
Self {
|
||||
components: components.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, id: Option<String>, component: Arc<dyn Component>) {
|
||||
self.components.push((id, component));
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.components.len()
|
||||
}
|
||||
|
||||
pub fn build(self) -> TwilightComponent {
|
||||
TwilightComponent::ActionRow(TwilightActionRow {
|
||||
components: self
|
||||
.components
|
||||
.into_iter()
|
||||
.map(|(id, c)| c.build(id))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Component: Send + Sync {
|
||||
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_trait]
|
||||
impl<T> Component for T
|
||||
where
|
||||
T: Button,
|
||||
{
|
||||
fn custom_id(&self) -> Option<String> {
|
||||
Button::id(self)
|
||||
}
|
||||
|
||||
fn build(&self, id: Option<String>) -> TwilightComponent {
|
||||
TwilightComponent::Button(TwilightButton {
|
||||
custom_id: id,
|
||||
disabled: false,
|
||||
emoji: None,
|
||||
label: self.label().map(|i| i.to_string()),
|
||||
style: if self.url().is_none() {
|
||||
self.style()
|
||||
} else {
|
||||
ButtonStyle::Link
|
||||
},
|
||||
url: self.url().map(|i| i.to_string()),
|
||||
sku_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, ctx: InteractionContext) -> Result<(), InteractionError> {
|
||||
self.on_press(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Button: Send + Sync {
|
||||
fn id(&self) -> Option<String> {
|
||||
if self.url().is_none() {
|
||||
Some(String::from("Button"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn style(&self) -> ButtonStyle {
|
||||
ButtonStyle::Secondary
|
||||
}
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn url(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn on_press(&self, ctx: InteractionContext) -> Result<(), InteractionError> {
|
||||
ctx.defer().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
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}
|
||||
};
|
||||
|
||||
pub struct InteractionContext {
|
||||
pub event: InteractionCreate,
|
||||
pub handlers: Arc<Handlers>,
|
||||
}
|
||||
|
||||
impl InteractionContext {
|
||||
pub fn new(
|
||||
event: InteractionCreate,
|
||||
handlers: Arc<Handlers>,
|
||||
) -> Self {
|
||||
Self {
|
||||
event,
|
||||
handlers
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn defer(&self) -> Result<(), InteractionError> {
|
||||
self.handlers.http
|
||||
.interaction(self.handlers.app.id)
|
||||
.create_response(
|
||||
self.event.id,
|
||||
&self.event.token,
|
||||
&InteractionResponse {
|
||||
data: None,
|
||||
kind: InteractionResponseType::DeferredUpdateMessage,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|_| InteractionError::DeferringFailed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn think(&self) -> Result<(), InteractionError> {
|
||||
self.handlers.http
|
||||
.interaction(self.handlers.app.id)
|
||||
.create_response(
|
||||
self.event.id,
|
||||
&self.event.token,
|
||||
&InteractionResponse {
|
||||
data: None,
|
||||
kind: InteractionResponseType::DeferredChannelMessageWithSource,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|_| InteractionError::DeferringFailed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn original_message(&self) -> Option<OriginalMessage> {
|
||||
self.event.message.as_ref().map(|_| OriginalMessage {
|
||||
interaction_id: self.event.id,
|
||||
interaction_token: self.event.token.clone(),
|
||||
handlers: self.handlers.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OriginalMessage {
|
||||
interaction_id: Id<InteractionMarker>,
|
||||
interaction_token: String,
|
||||
handlers: Arc<Handlers>
|
||||
}
|
||||
|
||||
impl OriginalMessage {
|
||||
pub fn edit(&self) -> OriginalMessageUpdater {
|
||||
OriginalMessageUpdater::new(
|
||||
self.interaction_id,
|
||||
self.interaction_token.clone(),
|
||||
self.handlers.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod components;
|
||||
pub mod context;
|
||||
pub mod registry;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InteractionError {
|
||||
#[error(
|
||||
"The ID for this interaction did not point to any existing interaction in the registry"
|
||||
)]
|
||||
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,
|
||||
|
||||
#[error("The original message was attempted to be edited, but it failed")]
|
||||
OriginalMessageEditingFailed,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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 super::components::Component;
|
||||
|
||||
pub struct Registry {
|
||||
inner: HashMap<String, Arc<dyn Component>>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&self, id: impl ToString, component: Arc<dyn Component>) {
|
||||
self.inner.upsert(id.to_string(), component);
|
||||
}
|
||||
|
||||
pub fn unregister(&self, id: impl ToString) {
|
||||
self.inner.remove(&id.to_string());
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
event: InteractionCreate,
|
||||
handlers: Arc<Handlers>,
|
||||
) -> 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 {
|
||||
component
|
||||
.execute(InteractionContext::new(event, handlers))
|
||||
.await
|
||||
} else {
|
||||
Err(InteractionError::InteractionNotFound)
|
||||
}
|
||||
}
|
||||
_ => Err(InteractionError::InteractionTypeUnsent),
|
||||
}
|
||||
} else {
|
||||
Err(InteractionError::InteractionTypeUnsent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod commands;
|
||||
pub mod interactions;
|
||||
pub mod utils;
|
||||
pub mod handlers;
|
||||
@@ -0,0 +1,33 @@
|
||||
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