Add support for extras, conditionally displaying commands in the help command, and start working on moderation commands
This commit is contained in:
Generated
+1
-1
@@ -1437,7 +1437,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "payphone"
|
name = "payphone"
|
||||||
version = "4.1.0"
|
version = "4.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "payphone"
|
name = "payphone"
|
||||||
version = "4.1.0"
|
version = "4.1.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
default-run = "payphone"
|
default-run = "payphone"
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,15 @@ pub enum Commands {
|
|||||||
default_values = &["redis://127.0.0.1:6379"],
|
default_values = &["redis://127.0.0.1:6379"],
|
||||||
)]
|
)]
|
||||||
redis_urls: Vec<String>,
|
redis_urls: Vec<String>,
|
||||||
|
|
||||||
|
/// The bot owner's ID, this user will be allowed to assign moderators.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
env = "PAYPHONE_OWNER_ID",
|
||||||
|
value_name = "ID",
|
||||||
|
default_value_t = 0
|
||||||
|
)]
|
||||||
|
bot_owner_id: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Starts a documentation server for the project which serves `cargo doc` and refreshes on
|
/// Starts a documentation server for the project which serves `cargo doc` and refreshes on
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS payphone.users_by_name AS
|
||||||
|
SELECT user_id, name FROM payphone.users
|
||||||
|
WHERE user_id IS NOT NULL AND name IS NOT NULL
|
||||||
|
PRIMARY KEY (name, user_id);
|
||||||
+96
-34
@@ -172,15 +172,48 @@ impl User {
|
|||||||
.into_rows_result()
|
.into_rows_result()
|
||||||
.map_err(|_| DatabaseError::ResultNotRows)?;
|
.map_err(|_| DatabaseError::ResultNotRows)?;
|
||||||
|
|
||||||
match result.single_row::<<User as Object>::Row>() {
|
get_single_row_from_result(&result).await
|
||||||
Ok(row) => Ok(Some(Self::from_row(row))),
|
|
||||||
Err(error) => match error {
|
|
||||||
SingleRowError::DeserializationFailed(_) | SingleRowError::TypeCheckFailed(_) => {
|
|
||||||
Err(DatabaseError::DeserializationFailed)
|
|
||||||
}
|
}
|
||||||
SingleRowError::UnexpectedRowCount(_) => Ok(None),
|
|
||||||
},
|
pub async fn get_by_name(
|
||||||
|
name: String,
|
||||||
|
session: &Session,
|
||||||
|
) -> Result<Option<Self>, DatabaseError> {
|
||||||
|
let Some(user_by_name) = UserByName::get(name, session).await? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::get_by_id(user_by_name.user_id, session).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a user from the DB and sets their last seen timestamp or inserts a new user from Discord.
|
||||||
|
///
|
||||||
|
/// It also updates the user's username.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `user` - The Twilight user to get or create.
|
||||||
|
/// * `session` - The Scylla session to use for database operations.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`Result<User, DatabaseError>`] - The user from the database or a new user created from
|
||||||
|
/// Discord.
|
||||||
|
pub async fn get_or_create_and_set_last_seen(
|
||||||
|
user: TwilightUser,
|
||||||
|
session: &Session,
|
||||||
|
) -> Result<Self, DatabaseError> {
|
||||||
|
let db_user = User::get_by_id(user.id.get(), session).await?;
|
||||||
|
|
||||||
|
if let Some(mut db_user) = db_user {
|
||||||
|
db_user.last_seen_at = UtcDateTime::now();
|
||||||
|
db_user.name = user.name;
|
||||||
|
|
||||||
|
return Ok(db_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_user = User::from(user);
|
||||||
|
new_user.insert(session).await?;
|
||||||
|
|
||||||
|
Ok(new_user)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> {
|
pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> {
|
||||||
@@ -272,34 +305,50 @@ impl From<TwilightUser> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a user from the DB and sets their last seen timestamp or inserts a new user from Discord.
|
#[derive(Debug, Clone)]
|
||||||
///
|
struct UserByName {
|
||||||
/// It also updates the user's username.
|
pub user_id: u64,
|
||||||
///
|
pub name: String,
|
||||||
/// Arguments:
|
}
|
||||||
/// * `user` - The Twilight user to get or create.
|
|
||||||
/// * `session` - The Scylla session to use for database operations.
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// [`Result<User, DatabaseError>`] - The user from the database or a new user created from
|
|
||||||
/// Discord.
|
|
||||||
pub async fn get_or_create_user_and_set_last_seen(
|
|
||||||
user: TwilightUser,
|
|
||||||
session: &Session,
|
|
||||||
) -> Result<User, DatabaseError> {
|
|
||||||
let db_user = User::get_by_id(user.id.get(), session).await?;
|
|
||||||
|
|
||||||
if let Some(mut db_user) = db_user {
|
impl<'a> Object<'a> for UserByName {
|
||||||
db_user.last_seen_at = UtcDateTime::now();
|
type Row = (i64, String);
|
||||||
db_user.name = user.name;
|
|
||||||
|
|
||||||
return Ok(db_user);
|
fn from_row(row: Self::Row) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: i64_to_u64(row.0),
|
||||||
|
name: row.1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_user = User::from(user);
|
fn to_row(value: Self) -> Self::Row {
|
||||||
new_user.insert(session).await?;
|
(u64_to_i64(value.user_id), value.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(new_user)
|
impl UserByName {
|
||||||
|
async fn get(name: String, session: &Session) -> Result<Option<Self>, DatabaseError> {
|
||||||
|
let result = session
|
||||||
|
.query_unpaged(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
user_id,
|
||||||
|
name
|
||||||
|
FROM
|
||||||
|
payphone.users_by_name
|
||||||
|
WHERE
|
||||||
|
name = ?
|
||||||
|
LIMIT 1;
|
||||||
|
"#,
|
||||||
|
(name,),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(DatabaseError::QueryError)?
|
||||||
|
.into_rows_result()
|
||||||
|
.map_err(|_| DatabaseError::ResultNotRows)?;
|
||||||
|
|
||||||
|
get_single_row_from_result(&result).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -1192,8 +1241,19 @@ impl Message {
|
|||||||
.map_err(|_| DatabaseError::ResultNotRows)?;
|
.map_err(|_| DatabaseError::ResultNotRows)?;
|
||||||
|
|
||||||
if result.rows_num() == 0 {
|
if result.rows_num() == 0 {
|
||||||
|
Self::get_by_propagation_message_id(message_id, session).await
|
||||||
|
} else {
|
||||||
|
get_single_row_from_result(&result).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_by_propagation_message_id(
|
||||||
|
propagation_message_id: u64,
|
||||||
|
session: &Session,
|
||||||
|
) -> Result<Option<Self>, DatabaseError> {
|
||||||
let message_by_propagation_id =
|
let message_by_propagation_id =
|
||||||
MessageByPropagationId::get_by_propagation_message_id(message_id, session).await?;
|
MessageByPropagationId::get_by_propagation_message_id(propagation_message_id, session)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(message_by_propagation_id) = message_by_propagation_id {
|
if let Some(message_by_propagation_id) = message_by_propagation_id {
|
||||||
let result = session
|
let result = session
|
||||||
@@ -1211,7 +1271,10 @@ impl Message {
|
|||||||
call_id = ? AND message_id = ?
|
call_id = ? AND message_id = ?
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
"#,
|
"#,
|
||||||
(call_id, u64_to_i64(message_by_propagation_id.message_id)),
|
(
|
||||||
|
u64_to_i64(message_by_propagation_id.call_id),
|
||||||
|
u64_to_i64(message_by_propagation_id.message_id),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(DatabaseError::QueryError)?
|
.map_err(DatabaseError::QueryError)?
|
||||||
@@ -1220,9 +1283,8 @@ impl Message {
|
|||||||
|
|
||||||
return get_single_row_from_result(&result).await;
|
return get_single_row_from_result(&result).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get_single_row_from_result(&result).await
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> {
|
pub async fn insert(&self, session: &Session) -> Result<(), DatabaseError> {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use crate::database::models::User;
|
||||||
|
|
||||||
|
pub struct Extras {
|
||||||
|
pub user: User,
|
||||||
|
}
|
||||||
+55
-40
@@ -9,7 +9,7 @@ use twilight_model::gateway::payload::outgoing::update_presence::UpdatePresenceP
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
framework::{
|
framework::{
|
||||||
commands::{Command, runner::CommandRunner},
|
commands::{Command, extras::ExtrasGetter, runner::CommandRunner},
|
||||||
events::traits::{
|
events::traits::{
|
||||||
OnInteractionHandler, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
|
OnInteractionHandler, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
|
||||||
OnReadyHandler, OnReconnectHandler, OnServerCreateHandler, OnServerDeleteHandler,
|
OnReadyHandler, OnReconnectHandler, OnServerCreateHandler, OnServerDeleteHandler,
|
||||||
@@ -32,7 +32,7 @@ pub enum RuntimeError {
|
|||||||
|
|
||||||
/// Getter trait for command prefixes.
|
/// Getter trait for command prefixes.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PrefixGetter<T> {
|
pub trait PrefixGetter<T, E> {
|
||||||
/// Get the command prefixes for the given server and user IDs.
|
/// Get the command prefixes for the given server and user IDs.
|
||||||
///
|
///
|
||||||
/// The getter must never fail. In case an internal process does, it must fall back to default
|
/// The getter must never fail. In case an internal process does, it must fall back to default
|
||||||
@@ -49,75 +49,87 @@ pub trait PrefixGetter<T> {
|
|||||||
/// first one will always be the safest choice.
|
/// first one will always be the safest choice.
|
||||||
async fn get(
|
async fn get(
|
||||||
&self,
|
&self,
|
||||||
runtime: &Runtime<T>,
|
runtime: &Runtime<T, E>,
|
||||||
server_id: Option<u64>,
|
server_id: Option<u64>,
|
||||||
user_id: Option<u64>,
|
user_id: Option<u64>,
|
||||||
) -> Vec<String>;
|
) -> Vec<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T> PrefixGetter<T> for String {
|
impl<T, E> PrefixGetter<T, E> for String {
|
||||||
async fn get(&self, _: &Runtime<T>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
async fn get(&self, _: &Runtime<T, E>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
||||||
vec![self.to_string()]
|
vec![self.to_string()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T> PrefixGetter<T> for &str {
|
impl<T, E> PrefixGetter<T, E> for &str {
|
||||||
async fn get(&self, _: &Runtime<T>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
async fn get(&self, _: &Runtime<T, E>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
||||||
vec![self.to_string()]
|
vec![self.to_string()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Send + Sync + 'static, const N: usize, S: Send + Sync + 'static> PrefixGetter<S> for [T; N]
|
impl<T, const N: usize, S, E> PrefixGetter<S, E> for [T; N]
|
||||||
where
|
where
|
||||||
T: ToString,
|
T: ToString + Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
async fn get(&self, _: &Runtime<S>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
async fn get(&self, _: &Runtime<S, E>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
||||||
self.iter().map(|s| s.to_string()).collect()
|
self.iter().map(|s| s.to_string()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<F, Fut, T> PrefixGetter<T> for F
|
impl<F, Fut, T, E> PrefixGetter<T, E> for F
|
||||||
where
|
where
|
||||||
F: Fn() -> Fut + Send + Sync + 'static,
|
F: Fn() -> Fut + Send + Sync + 'static,
|
||||||
Fut: Future<Output = Vec<String>> + Send,
|
Fut: Future<Output = Vec<String>> + Send,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
async fn get(&self, _: &Runtime<T>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
async fn get(&self, _: &Runtime<T, E>, _: Option<u64>, _: Option<u64>) -> Vec<String> {
|
||||||
(self)().await
|
(self)().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Bot<T: Send + Sync> {
|
pub struct Bot<T: Send + Sync, E: Send + Sync = ()> {
|
||||||
shard: ShardId,
|
shard: ShardId,
|
||||||
state: Arc<T>,
|
state: Arc<T>,
|
||||||
presence: Option<UpdatePresencePayload>,
|
presence: Option<UpdatePresencePayload>,
|
||||||
prefixes: Arc<dyn PrefixGetter<T> + Send + Sync>,
|
prefixes: Arc<dyn PrefixGetter<T, E> + Send + Sync>,
|
||||||
intents: Intents,
|
intents: Intents,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
cached_resource_types: Vec<ResourceType>,
|
cached_resource_types: Vec<ResourceType>,
|
||||||
commands: Vec<Arc<dyn Command<T>>>,
|
commands: Vec<Arc<dyn Command<T, E>>>,
|
||||||
components: Vec<Arc<dyn Component<T>>>,
|
components: Vec<Arc<dyn Component<T, E>>>,
|
||||||
on_ready_handlers: Vec<Arc<dyn OnReadyHandler<T>>>,
|
on_ready_handlers: Vec<Arc<dyn OnReadyHandler<T, E>>>,
|
||||||
on_reconnect_handlers: Vec<Arc<dyn OnReconnectHandler<T>>>,
|
on_reconnect_handlers: Vec<Arc<dyn OnReconnectHandler<T, E>>>,
|
||||||
on_interaction_handlers: Vec<Arc<dyn OnInteractionHandler<T>>>,
|
on_interaction_handlers: Vec<Arc<dyn OnInteractionHandler<T, E>>>,
|
||||||
on_message_handlers: Vec<Arc<dyn OnMessageHandler<T>>>,
|
on_message_handlers: Vec<Arc<dyn OnMessageHandler<T, E>>>,
|
||||||
on_message_edit_handlers: Vec<Arc<dyn OnMessageEditHandler<T>>>,
|
on_message_edit_handlers: Vec<Arc<dyn OnMessageEditHandler<T, E>>>,
|
||||||
on_message_delete_handlers: Vec<Arc<dyn OnMessageDeleteHandler<T>>>,
|
on_message_delete_handlers: Vec<Arc<dyn OnMessageDeleteHandler<T, E>>>,
|
||||||
on_server_create_handlers: Vec<Arc<dyn OnServerCreateHandler<T>>>,
|
on_server_create_handlers: Vec<Arc<dyn OnServerCreateHandler<T, E>>>,
|
||||||
on_server_update_handlers: Vec<Arc<dyn OnServerUpdateHandler<T>>>,
|
on_server_update_handlers: Vec<Arc<dyn OnServerUpdateHandler<T, E>>>,
|
||||||
on_server_delete_handlers: Vec<Arc<dyn OnServerDeleteHandler<T>>>,
|
on_server_delete_handlers: Vec<Arc<dyn OnServerDeleteHandler<T, E>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static + Send + Sync> Bot<T> {
|
impl<T, E> Bot<T, E>
|
||||||
pub fn new(state: T, prefixes: impl PrefixGetter<T> + Send + Sync + 'static) -> Self {
|
where
|
||||||
|
T: 'static + Send + Sync,
|
||||||
|
E: 'static + Send + Sync,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
state: T,
|
||||||
|
prefixes: impl PrefixGetter<T, E> + Send + Sync + 'static,
|
||||||
|
extras: impl ExtrasGetter<T, Extras = E> + 'static,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shard: ShardId::new(0, 1),
|
shard: ShardId::new(0, 1),
|
||||||
state: Arc::new(state),
|
state: Arc::new(state),
|
||||||
presence: None,
|
presence: None,
|
||||||
prefixes: Arc::new(prefixes),
|
prefixes: Arc::new(prefixes),
|
||||||
intents: Intents::empty(),
|
intents: Intents::empty(),
|
||||||
|
extras: Arc::new(extras),
|
||||||
cached_resource_types: Vec::new(),
|
cached_resource_types: Vec::new(),
|
||||||
commands: Vec::new(),
|
commands: Vec::new(),
|
||||||
components: Vec::new(),
|
components: Vec::new(),
|
||||||
@@ -148,12 +160,12 @@ impl<T: 'static + Send + Sync> Bot<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command(mut self, command: impl Command<T> + 'static) -> Self {
|
pub fn command(mut self, command: impl Command<T, E> + 'static) -> Self {
|
||||||
self.commands.push(Arc::new(command));
|
self.commands.push(Arc::new(command));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component(mut self, component: impl Component<T> + 'static) -> Self {
|
pub fn component(mut self, component: impl Component<T, E> + 'static) -> Self {
|
||||||
self.components.push(Arc::new(component));
|
self.components.push(Arc::new(component));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -166,50 +178,53 @@ impl<T: 'static + Send + Sync> Bot<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_ready(mut self, handler: impl OnReadyHandler<T> + 'static) -> Self {
|
pub fn on_ready(mut self, handler: impl OnReadyHandler<T, E> + 'static) -> Self {
|
||||||
self.on_ready_handlers.push(Arc::new(handler));
|
self.on_ready_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_reconnect(mut self, handler: impl OnReconnectHandler<T> + 'static) -> Self {
|
pub fn on_reconnect(mut self, handler: impl OnReconnectHandler<T, E> + 'static) -> Self {
|
||||||
self.on_reconnect_handlers.push(Arc::new(handler));
|
self.on_reconnect_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn on_interaction(mut self, handler: impl OnInteractionHandler<T> + 'static) -> Self {
|
pub fn on_interaction(mut self, handler: impl OnInteractionHandler<T, E> + 'static) -> Self {
|
||||||
self.on_interaction_handlers.push(Arc::new(handler));
|
self.on_interaction_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_message(mut self, handler: impl OnMessageHandler<T> + 'static) -> Self {
|
pub fn on_message(mut self, handler: impl OnMessageHandler<T, E> + 'static) -> Self {
|
||||||
self.on_message_handlers.push(Arc::new(handler));
|
self.on_message_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn on_message_edit(mut self, handler: impl OnMessageEditHandler<T> + 'static) -> Self {
|
pub fn on_message_edit(mut self, handler: impl OnMessageEditHandler<T, E> + 'static) -> Self {
|
||||||
self.on_message_edit_handlers.push(Arc::new(handler));
|
self.on_message_edit_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn on_message_delete(mut self, handler: impl OnMessageDeleteHandler<T> + 'static) -> Self {
|
pub fn on_message_delete(
|
||||||
|
mut self,
|
||||||
|
handler: impl OnMessageDeleteHandler<T, E> + 'static,
|
||||||
|
) -> Self {
|
||||||
self.on_message_delete_handlers.push(Arc::new(handler));
|
self.on_message_delete_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_server_create(mut self, handler: impl OnServerCreateHandler<T> + 'static) -> Self {
|
pub fn on_server_create(mut self, handler: impl OnServerCreateHandler<T, E> + 'static) -> Self {
|
||||||
self.on_server_create_handlers.push(Arc::new(handler));
|
self.on_server_create_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_server_update(mut self, handler: impl OnServerUpdateHandler<T> + 'static) -> Self {
|
pub fn on_server_update(mut self, handler: impl OnServerUpdateHandler<T, E> + 'static) -> Self {
|
||||||
self.on_server_update_handlers.push(Arc::new(handler));
|
self.on_server_update_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_server_delete(mut self, handler: impl OnServerDeleteHandler<T> + 'static) -> Self {
|
pub fn on_server_delete(mut self, handler: impl OnServerDeleteHandler<T, E> + 'static) -> Self {
|
||||||
self.on_server_delete_handlers.push(Arc::new(handler));
|
self.on_server_delete_handlers.push(Arc::new(handler));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -302,7 +317,7 @@ impl<T: 'static + Send + Sync> Bot<T> {
|
|||||||
Event::InteractionCreate(interaction) => {
|
Event::InteractionCreate(interaction) => {
|
||||||
for handler in &task_bot.on_interaction_handlers {
|
for handler in &task_bot.on_interaction_handlers {
|
||||||
handler
|
handler
|
||||||
.on_interaction(&interaction, task_rt.clone())
|
.on_interaction(&interaction, task_rt.clone(), task_bot.extras.clone())
|
||||||
.await
|
.await
|
||||||
.inspect_err(
|
.inspect_err(
|
||||||
|e| log::error!("An error occurred while handling on interaction for {handler:?}! {e:?}")
|
|e| log::error!("An error occurred while handling on interaction for {handler:?}! {e:?}")
|
||||||
@@ -315,7 +330,7 @@ impl<T: 'static + Send + Sync> Bot<T> {
|
|||||||
log::trace!("Running {handler:?}");
|
log::trace!("Running {handler:?}");
|
||||||
|
|
||||||
handler
|
handler
|
||||||
.on_message(&message, task_rt.clone())
|
.on_message(&message, task_rt.clone(), task_bot.extras.clone())
|
||||||
.await
|
.await
|
||||||
.inspect_err(
|
.inspect_err(
|
||||||
|e| log::error!("An error occurred while handling on message for {handler:?}! {e:?}")
|
|e| log::error!("An error occurred while handling on message for {handler:?}! {e:?}")
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use std::{
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::framework::commands::context::CommandContext;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ArgumentParsingError {
|
pub enum ArgumentParsingError {
|
||||||
#[error("This argument was missing in the arguments array")]
|
#[error("This argument was missing in the arguments array")]
|
||||||
@@ -12,14 +14,23 @@ pub enum ArgumentParsingError {
|
|||||||
|
|
||||||
#[error("An argument was there but it could not be parsed into the specified value")]
|
#[error("An argument was there but it could not be parsed into the specified value")]
|
||||||
InvalidArgument,
|
InvalidArgument,
|
||||||
|
|
||||||
|
#[error("A database operation failed")]
|
||||||
|
DatabaseFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromArguments: Sized {
|
pub trait FromArguments<S, E>: Sized {
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError>;
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromArguments for String {
|
impl<S, E> FromArguments<S, E> for String {
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
match args.split_once(" ") {
|
match args.split_once(" ") {
|
||||||
Some((arg, rest)) => Ok((arg.to_string(), rest)),
|
Some((arg, rest)) => Ok((arg.to_string(), rest)),
|
||||||
@@ -37,8 +48,11 @@ impl FromArguments for String {
|
|||||||
/// Parsing this argument type never fails.
|
/// Parsing this argument type never fails.
|
||||||
pub struct GreedyString(pub String);
|
pub struct GreedyString(pub String);
|
||||||
|
|
||||||
impl FromArguments for GreedyString {
|
impl<S, E> FromArguments<S, E> for GreedyString {
|
||||||
fn from_args<'a>(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
let value = GreedyString(args.to_string());
|
let value = GreedyString(args.to_string());
|
||||||
|
|
||||||
Ok((value, ""))
|
Ok((value, ""))
|
||||||
@@ -87,8 +101,11 @@ impl fmt::Display for NeedyString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromArguments for NeedyString {
|
impl<S, E> FromArguments<S, E> for NeedyString {
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
match args.split_once(" ") {
|
match args.split_once(" ") {
|
||||||
Some((arg, rest)) => Ok((Self(arg.to_string()), rest)),
|
Some((arg, rest)) => Ok((Self(arg.to_string()), rest)),
|
||||||
@@ -122,8 +139,11 @@ impl fmt::Display for NeedyGreedyString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromArguments for NeedyGreedyString {
|
impl<S, E> FromArguments<S, E> for NeedyGreedyString {
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
Ok((Self(args.to_string()), ""))
|
Ok((Self(args.to_string()), ""))
|
||||||
} else {
|
} else {
|
||||||
@@ -132,8 +152,11 @@ impl FromArguments for NeedyGreedyString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromArguments for bool {
|
impl<S, E> FromArguments<S, E> for bool {
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
let arg: &str;
|
let arg: &str;
|
||||||
let rest: &str;
|
let rest: &str;
|
||||||
|
|
||||||
@@ -156,73 +179,86 @@ impl FromArguments for bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> FromArguments for Option<T>
|
impl<T, S, E> FromArguments<S, E> for Option<T>
|
||||||
where
|
where
|
||||||
T: FromArguments,
|
T: FromArguments<S, E>,
|
||||||
{
|
{
|
||||||
fn from_args(args: &str) -> Result<(Self, &str), ArgumentParsingError> {
|
async fn from_args<'a>(
|
||||||
match T::from_args(args) {
|
args: &'a str,
|
||||||
|
_ctx: &'a CommandContext<'_, S, E>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
|
match T::from_args(args, _ctx).await {
|
||||||
Ok((result, rest)) => Ok((Some(result), rest)),
|
Ok((result, rest)) => Ok((Some(result), rest)),
|
||||||
Err(_) => Ok((None, args)),
|
Err(_) => Ok((None, args)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ParseArguments: Sized {
|
pub struct Arg<T>(pub T);
|
||||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParseArguments for () {
|
impl<T> Deref for Arg<T> {
|
||||||
fn parse(_: &str) -> Result<Self, ArgumentParsingError> {
|
type Target = T;
|
||||||
Ok(())
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> ParseArguments for A
|
impl<T> DerefMut for Arg<T> {
|
||||||
where
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
A: FromArguments,
|
&mut self.0
|
||||||
{
|
|
||||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
|
||||||
let (a, _) = A::from_args(args)?;
|
|
||||||
|
|
||||||
Ok(a)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> ParseArguments for (A,)
|
pub trait ParseArguments<S, E>: Sized {
|
||||||
|
async fn parse(args: &str, ctx: &CommandContext<S, E>) -> Result<Self, ArgumentParsingError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, E> ParseArguments<S, E> for Arg<T>
|
||||||
where
|
where
|
||||||
A: FromArguments,
|
T: FromArguments<S, E>,
|
||||||
{
|
{
|
||||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
async fn parse(args: &str, ctx: &CommandContext<'_, S, E>) -> Result<Self, ArgumentParsingError> {
|
||||||
let (a, _) = A::from_args(args)?;
|
let (value, _) = T::from_args(args, ctx).await?;
|
||||||
|
|
||||||
|
Ok(Arg(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, S, E> ParseArguments<S, E> for (A,)
|
||||||
|
where
|
||||||
|
A: FromArguments<S, E>,
|
||||||
|
{
|
||||||
|
async fn parse(args: &str, ctx: &CommandContext<'_, S, E>) -> Result<Self, ArgumentParsingError> {
|
||||||
|
let (a, _) = A::from_args(args, ctx).await?;
|
||||||
|
|
||||||
Ok((a,))
|
Ok((a,))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> ParseArguments for (A, B)
|
impl<A, B, S, E> ParseArguments<S, E> for (A, B)
|
||||||
where
|
where
|
||||||
A: FromArguments,
|
A: FromArguments<S, E>,
|
||||||
B: FromArguments,
|
B: FromArguments<S, E>,
|
||||||
{
|
{
|
||||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
async fn parse(args: &str, ctx: &CommandContext<'_, S, E>) -> Result<Self, ArgumentParsingError> {
|
||||||
let (a, args) = A::from_args(args)?;
|
let (a, args) = A::from_args(args, ctx).await?;
|
||||||
let (b, _) = B::from_args(args)?;
|
let (b, _) = B::from_args(args, ctx).await?;
|
||||||
|
|
||||||
Ok((a, b))
|
Ok((a, b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B, C> ParseArguments for (A, B, C)
|
impl<A, B, C, S, E> ParseArguments<S, E> for (A, B, C)
|
||||||
where
|
where
|
||||||
A: FromArguments,
|
A: FromArguments<S, E>,
|
||||||
B: FromArguments,
|
B: FromArguments<S, E>,
|
||||||
C: FromArguments,
|
C: FromArguments<S, E>,
|
||||||
{
|
{
|
||||||
fn parse(args: &str) -> Result<Self, ArgumentParsingError> {
|
async fn parse(args: &str, ctx: &CommandContext<'_, S, E>) -> Result<Self, ArgumentParsingError> {
|
||||||
let (a, args) = A::from_args(args)?;
|
let (a, args) = A::from_args(args, ctx).await?;
|
||||||
let (b, args) = B::from_args(args)?;
|
let (b, args) = B::from_args(args, ctx).await?;
|
||||||
let (c, _) = C::from_args(args)?;
|
let (c, _) = C::from_args(args, ctx).await?;
|
||||||
|
|
||||||
Ok((a, b, c))
|
Ok((a, b, c))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,33 @@ use twilight_model::gateway::payload::incoming::MessageCreate;
|
|||||||
|
|
||||||
use crate::framework::{commands::messages::MessageSender, runtime::Runtime};
|
use crate::framework::{commands::messages::MessageSender, runtime::Runtime};
|
||||||
|
|
||||||
pub struct CommandContext<'a, T> {
|
pub struct CommandContext<'a, T, E> {
|
||||||
pub event: &'a MessageCreate,
|
pub event: &'a MessageCreate,
|
||||||
pub rt: Arc<Runtime<T>>,
|
pub rt: Arc<Runtime<T, E>>,
|
||||||
pub prefix: &'a str,
|
pub prefix: &'a str,
|
||||||
|
pub extras: E,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> CommandContext<'a, T>
|
impl<'a, T, E> CommandContext<'a, T, E>
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(event: &'a MessageCreate, rt: Arc<Runtime<T>>, prefix: &'a str) -> Self {
|
pub fn new(
|
||||||
Self { event, rt, prefix }
|
event: &'a MessageCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
prefix: &'a str,
|
||||||
|
extras: E,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
event,
|
||||||
|
rt,
|
||||||
|
prefix,
|
||||||
|
extras,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send(&self) -> MessageSender<T> {
|
pub fn send(&self) -> MessageSender<T, E> {
|
||||||
MessageSender::new(self.event.channel_id, self.rt.clone())
|
MessageSender::new(self.event.channel_id, self.rt.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use twilight_model::user::User;
|
||||||
|
|
||||||
|
use crate::framework::runtime::Runtime;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ExtrasGetter<S>: Send + Sync {
|
||||||
|
type Extras;
|
||||||
|
|
||||||
|
async fn get_extra(
|
||||||
|
&self,
|
||||||
|
user: &User,
|
||||||
|
rt: &Runtime<S, Self::Extras>,
|
||||||
|
) -> Result<Self::Extras, Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultExtraGetter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<S> ExtrasGetter<S> for DefaultExtraGetter {
|
||||||
|
type Extras = ();
|
||||||
|
|
||||||
|
async fn get_extra(
|
||||||
|
&self,
|
||||||
|
_user: &User,
|
||||||
|
_rt: &Runtime<S, Self::Extras>,
|
||||||
|
) -> Result<Self::Extras, Box<dyn Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,23 +23,24 @@ use crate::framework::{
|
|||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct MessageSender<T> {
|
pub struct MessageSender<T, E> {
|
||||||
channel_id: Id<ChannelMarker>,
|
channel_id: Id<ChannelMarker>,
|
||||||
|
|
||||||
rt: Arc<Runtime<T>>,
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed>,
|
||||||
|
|
||||||
// Row index, component
|
// Row index, component
|
||||||
components: Vec<(u8, Arc<dyn Component<T>>)>,
|
components: Vec<(u8, Arc<dyn Component<T, E>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> MessageSender<T>
|
impl<T, E> MessageSender<T, E>
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(channel_id: Id<ChannelMarker>, rt: Arc<Runtime<T>>) -> Self {
|
pub fn new(channel_id: Id<ChannelMarker>, rt: Arc<Runtime<T, E>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
channel_id,
|
channel_id,
|
||||||
rt,
|
rt,
|
||||||
@@ -61,7 +62,7 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component(mut self, component: impl Component<T> + 'static, row: u8) -> Self {
|
pub fn component(mut self, component: impl Component<T, E> + 'static, row: u8) -> Self {
|
||||||
self.components.push((row, Arc::new(component)));
|
self.components.push((row, Arc::new(component)));
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -120,7 +121,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync + 'static> IntoFuture for MessageSender<T> {
|
impl<T: Send + Sync + 'static, E: Send + Sync + 'static> IntoFuture for MessageSender<T, E> {
|
||||||
type Output = Result<Response<Message>, Error>;
|
type Output = Result<Response<Message>, Error>;
|
||||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
||||||
|
|
||||||
@@ -129,27 +130,28 @@ impl<T: Send + Sync + 'static> IntoFuture for MessageSender<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OriginalMessageUpdater<T> {
|
pub struct OriginalMessageUpdater<T, E> {
|
||||||
interaction_id: Id<InteractionMarker>,
|
interaction_id: Id<InteractionMarker>,
|
||||||
interaction_token: String,
|
interaction_token: String,
|
||||||
|
|
||||||
rt: Arc<Runtime<T>>,
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
embeds: Vec<Embed>,
|
embeds: Vec<Embed>,
|
||||||
|
|
||||||
// Row index, component
|
// Row index, component
|
||||||
components: Vec<(u8, Arc<dyn Component<T>>)>,
|
components: Vec<(u8, Arc<dyn Component<T, E>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> OriginalMessageUpdater<T>
|
impl<T, E> OriginalMessageUpdater<T, E>
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
interaction_id: Id<InteractionMarker>,
|
interaction_id: Id<InteractionMarker>,
|
||||||
interaction_token: String,
|
interaction_token: String,
|
||||||
rt: Arc<Runtime<T>>,
|
rt: Arc<Runtime<T, E>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
interaction_id,
|
interaction_id,
|
||||||
@@ -174,7 +176,7 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component(mut self, component: impl Component<T> + 'static, row: u8) -> Self {
|
pub fn component(mut self, component: impl Component<T, E> + 'static, row: u8) -> Self {
|
||||||
self.components.push((row, Arc::new(component)));
|
self.components.push((row, Arc::new(component)));
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -245,7 +247,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync + 'static> IntoFuture for OriginalMessageUpdater<T> {
|
impl<T: Send + Sync + 'static, E: Send + Sync + 'static> IntoFuture
|
||||||
|
for OriginalMessageUpdater<T, E>
|
||||||
|
{
|
||||||
type Output = Result<Response<EmptyBody>, Error>;
|
type Output = Result<Response<EmptyBody>, Error>;
|
||||||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use deadpool_redis::{cluster::PoolError, redis::RedisError};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use twilight_http::response::DeserializeBodyError;
|
use twilight_http::response::DeserializeBodyError;
|
||||||
|
|
||||||
use crate::framework::commands::{
|
use crate::framework::{commands::{
|
||||||
arguments::Argument, context::CommandContext, permissions::Permission,
|
arguments::Argument, context::CommandContext, permissions::Permission,
|
||||||
};
|
}, runtime::Runtime};
|
||||||
|
|
||||||
pub mod arguments;
|
pub mod arguments;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod extras;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
pub mod runner;
|
pub mod runner;
|
||||||
@@ -21,7 +22,7 @@ pub enum CommandError {
|
|||||||
MessageSendingFailed,
|
MessageSendingFailed,
|
||||||
|
|
||||||
#[error("A database query failed")]
|
#[error("A database query failed")]
|
||||||
DatabaseQueryFailed,
|
DatabaseFailed,
|
||||||
|
|
||||||
#[error("A database operation failed due to an inconsistent state")]
|
#[error("A database operation failed due to an inconsistent state")]
|
||||||
DatabaseInconsistentState,
|
DatabaseInconsistentState,
|
||||||
@@ -35,9 +36,6 @@ pub enum CommandError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DiscordDeserializationFailed(#[from] DeserializeBodyError),
|
DiscordDeserializationFailed(#[from] DeserializeBodyError),
|
||||||
|
|
||||||
#[error("The cache's state was inconsistent.")]
|
|
||||||
CacheStateInconsistent,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RedisPoolFailed(#[from] PoolError),
|
RedisPoolFailed(#[from] PoolError),
|
||||||
|
|
||||||
@@ -46,21 +44,29 @@ pub enum CommandError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Command<T>: Send + Sync + Debug {
|
pub trait Command<T, E>: Send + Sync + Debug {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
fn aliases(&self) -> &'static [&'static str] {
|
fn aliases(&self) -> &'static [&'static str] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> &'static str {
|
fn description(&self) -> &'static str {
|
||||||
"A Payphone command."
|
"A Payphone command."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arguments(&self) -> &'static [Argument] {
|
fn arguments(&self) -> &'static [Argument] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<T> + Send + Sync + 'static>> {
|
|
||||||
|
fn permissions(&self) -> Vec<Box<dyn Permission<T, E> + Send + Sync + 'static>> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_in_help(&self, rt: &Runtime<T, E>, extras: &E) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the command name + all its aliases.
|
/// Returns the command name + all its aliases.
|
||||||
///
|
///
|
||||||
/// Returns:
|
/// Returns:
|
||||||
@@ -71,7 +77,7 @@ pub trait Command<T>: Send + Sync + Debug {
|
|||||||
all
|
all
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self, ctx: CommandContext<'_, T>, args: &str) -> Result<(), CommandError>;
|
async fn execute(&self, ctx: CommandContext<'_, T, E>, args: &str) -> Result<(), CommandError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CommandParts<'a> {
|
pub struct CommandParts<'a> {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ use async_trait::async_trait;
|
|||||||
use crate::framework::commands::{CommandError, context::CommandContext};
|
use crate::framework::commands::{CommandError, context::CommandContext};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Permission<S: Send + Sync + 'static>: Debug {
|
pub trait Permission<S: Send + Sync + 'static, E>: Debug {
|
||||||
async fn check(&self, ctx: &CommandContext<S>) -> Result<bool, CommandError>;
|
async fn check(&self, ctx: &CommandContext<S, E>) -> Result<bool, CommandError>;
|
||||||
|
|
||||||
async fn on_handle_denied(&self, _ctx: &CommandContext<S>) -> Result<(), CommandError> {
|
async fn on_handle_denied(&self, _ctx: &CommandContext<S, E>) -> Result<(), CommandError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use async_trait::async_trait;
|
|||||||
use twilight_model::gateway::payload::incoming::MessageCreate;
|
use twilight_model::gateway::payload::incoming::MessageCreate;
|
||||||
|
|
||||||
use crate::framework::{
|
use crate::framework::{
|
||||||
commands::{context::CommandContext, get_command_parts},
|
commands::{context::CommandContext, extras::ExtrasGetter, get_command_parts},
|
||||||
events::traits::{EventResult, OnMessageHandler},
|
events::traits::{EventResult, OnMessageHandler},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
};
|
};
|
||||||
@@ -13,8 +13,17 @@ use crate::framework::{
|
|||||||
pub struct CommandRunner;
|
pub struct CommandRunner;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Send + Sync + 'static> OnMessageHandler<T> for CommandRunner {
|
impl<T, E> OnMessageHandler<T, E> for CommandRunner
|
||||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<T>>) -> EventResult {
|
where
|
||||||
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn on_message(
|
||||||
|
&self,
|
||||||
|
event: &MessageCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
|
) -> EventResult {
|
||||||
if event.author.bot || event.author.system.unwrap_or(false) {
|
if event.author.bot || event.author.system.unwrap_or(false) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -31,7 +40,12 @@ impl<T: Send + Sync + 'static> OnMessageHandler<T> for CommandRunner {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(command) = command {
|
if let Some(command) = command {
|
||||||
let ctx = CommandContext::new(event, rt.clone(), &prefixes[0]);
|
let extras = extras
|
||||||
|
.get_extra(&event.author, &rt)
|
||||||
|
.await
|
||||||
|
.inspect_err(|e| log::error!("Failed to get extras: {e}"))?;
|
||||||
|
|
||||||
|
let ctx = CommandContext::new(event, rt.clone(), &prefixes[0], extras);
|
||||||
|
|
||||||
for permission in command.permissions() {
|
for permission in command.permissions() {
|
||||||
if !permission
|
if !permission
|
||||||
|
|||||||
@@ -6,78 +6,98 @@ use twilight_model::gateway::payload::incoming::{
|
|||||||
MessageUpdate, Ready,
|
MessageUpdate, Ready,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::framework::runtime::Runtime;
|
use crate::framework::{commands::extras::ExtrasGetter, runtime::Runtime};
|
||||||
|
|
||||||
pub type EventResult = Result<(), Box<dyn Error>>;
|
pub type EventResult = Result<(), Box<dyn Error>>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnReadyHandler<T>: Send + Sync + Debug
|
pub trait OnReadyHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_ready(&self, event: &Ready, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_ready(&self, event: &Ready, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnReconnectHandler<T>: Send + Sync + Debug
|
pub trait OnReconnectHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_reconnect(&self, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_reconnect(&self, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnInteractionHandler<T>: Send + Sync + Debug
|
pub trait OnInteractionHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_interaction(&self, event: &InteractionCreate, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_interaction(
|
||||||
|
&self,
|
||||||
|
event: &InteractionCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
|
) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnMessageHandler<T>: Send + Sync + Debug
|
pub trait OnMessageHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_message(
|
||||||
|
&self,
|
||||||
|
event: &MessageCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
|
) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnMessageEditHandler<T>: Send + Sync + Debug
|
pub trait OnMessageEditHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnMessageDeleteHandler<T>: Send + Sync + Debug
|
pub trait OnMessageDeleteHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_message_delete(&self, event: &MessageDelete, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_message_delete(&self, event: &MessageDelete, rt: Arc<Runtime<T, E>>)
|
||||||
|
-> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnServerCreateHandler<T>: Send + Sync + Debug
|
pub trait OnServerCreateHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_server_create(&self, event: &GuildCreate, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_server_create(&self, event: &GuildCreate, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnServerUpdateHandler<T>: Send + Sync + Debug
|
pub trait OnServerUpdateHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_server_update(&self, event: &GuildUpdate, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_server_update(&self, event: &GuildUpdate, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OnServerDeleteHandler<T>: Send + Sync + Debug
|
pub trait OnServerDeleteHandler<T, E>: Send + Sync + Debug
|
||||||
where
|
where
|
||||||
T: Send + Sync,
|
T: Send + Sync,
|
||||||
|
E: Send + Sync,
|
||||||
{
|
{
|
||||||
async fn on_server_delete(&self, event: &GuildDelete, rt: Arc<Runtime<T>>) -> EventResult;
|
async fn on_server_delete(&self, event: &GuildDelete, rt: Arc<Runtime<T, E>>) -> EventResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ use twilight_model::channel::message::{
|
|||||||
|
|
||||||
use crate::framework::interactions::{InteractionError, context::InteractionContext};
|
use crate::framework::interactions::{InteractionError, context::InteractionContext};
|
||||||
|
|
||||||
type ComponentWithID<T> = (Option<String>, Arc<dyn Component<T>>);
|
type ComponentWithID<T, E> = (Option<String>, Arc<dyn Component<T, E>>);
|
||||||
|
|
||||||
pub struct ActionRow<T> {
|
pub struct ActionRow<T, E> {
|
||||||
components: Vec<ComponentWithID<T>>,
|
components: Vec<ComponentWithID<T, E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ActionRow<T> {
|
impl<T, E> ActionRow<T, E> {
|
||||||
pub fn new(components: &[ComponentWithID<T>]) -> Self {
|
pub fn new(components: &[ComponentWithID<T, E>]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
components: components.to_vec(),
|
components: components.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, id: Option<String>, component: Arc<dyn Component<T>>) {
|
pub fn add(&mut self, id: Option<String>, component: Arc<dyn Component<T, E>>) {
|
||||||
self.components.push((id, component));
|
self.components.push((id, component));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,19 +41,21 @@ impl<T> ActionRow<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Component<T>: Send + Sync + Debug {
|
pub trait Component<T, E>: Send + Sync + Debug {
|
||||||
fn custom_id(&self) -> Option<String> {
|
fn custom_id(&self) -> Option<String> {
|
||||||
Some(String::from("component"))
|
Some(String::from("component"))
|
||||||
}
|
}
|
||||||
fn build(&self, id: Option<String>) -> TwilightComponent;
|
fn build(&self, id: Option<String>) -> TwilightComponent;
|
||||||
|
|
||||||
async fn execute(&self, ctx: InteractionContext<T>) -> Result<(), InteractionError>;
|
async fn execute(&self, ctx: InteractionContext<T, E>) -> Result<(), InteractionError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T, S: Send + Sync + 'static> Component<S> for T
|
impl<T, S, E> Component<S, E> for T
|
||||||
where
|
where
|
||||||
T: Button<S>,
|
T: Button<S, E>,
|
||||||
|
S: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn custom_id(&self) -> Option<String> {
|
fn custom_id(&self) -> Option<String> {
|
||||||
Button::id(self)
|
Button::id(self)
|
||||||
@@ -75,13 +77,17 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self, ctx: InteractionContext<S>) -> Result<(), InteractionError> {
|
async fn execute(&self, ctx: InteractionContext<S, E>) -> Result<(), InteractionError> {
|
||||||
self.on_press(ctx).await
|
self.on_press(ctx).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Button<S: Send + Sync + 'static>: Send + Sync + Debug {
|
pub trait Button<S, E>: Send + Sync + Debug
|
||||||
|
where
|
||||||
|
S: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
|
{
|
||||||
fn id(&self) -> Option<String> {
|
fn id(&self) -> Option<String> {
|
||||||
if self.url().is_none() {
|
if self.url().is_none() {
|
||||||
Some(String::from("Button"))
|
Some(String::from("Button"))
|
||||||
@@ -99,7 +105,7 @@ pub trait Button<S: Send + Sync + 'static>: Send + Sync + Debug {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_press(&self, ctx: InteractionContext<S>) -> Result<(), InteractionError> {
|
async fn on_press(&self, ctx: InteractionContext<S, E>) -> Result<(), InteractionError> {
|
||||||
ctx.defer().await
|
ctx.defer().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,26 @@ use crate::framework::{
|
|||||||
commands::messages::OriginalMessageUpdater, interactions::InteractionError, runtime::Runtime,
|
commands::messages::OriginalMessageUpdater, interactions::InteractionError, runtime::Runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct InteractionContext<T> {
|
pub struct InteractionContext<T, E> {
|
||||||
pub event: InteractionCreate,
|
pub event: InteractionCreate,
|
||||||
pub rt: Arc<Runtime<T>>,
|
pub rt: Arc<Runtime<T, E>>,
|
||||||
pub prefix: String,
|
pub prefix: String,
|
||||||
|
pub extras: E,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> InteractionContext<T> {
|
impl<T, E> InteractionContext<T, E> {
|
||||||
pub fn new(event: InteractionCreate, rt: Arc<Runtime<T>>, prefix: String) -> Self {
|
pub fn new(
|
||||||
Self { event, rt, prefix }
|
event: InteractionCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
prefix: String,
|
||||||
|
extras: E,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
event,
|
||||||
|
rt,
|
||||||
|
prefix,
|
||||||
|
extras,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn defer(&self) -> Result<(), InteractionError> {
|
pub async fn defer(&self) -> Result<(), InteractionError> {
|
||||||
@@ -39,7 +50,7 @@ impl<T> InteractionContext<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn original_message(&self) -> Option<OriginalMessage<T>> {
|
pub fn original_message(&self) -> Option<OriginalMessage<T, E>> {
|
||||||
self.event.message.as_ref().map(|_| OriginalMessage {
|
self.event.message.as_ref().map(|_| OriginalMessage {
|
||||||
interaction_id: self.event.id,
|
interaction_id: self.event.id,
|
||||||
interaction_token: self.event.token.clone(),
|
interaction_token: self.event.token.clone(),
|
||||||
@@ -48,17 +59,18 @@ impl<T> InteractionContext<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OriginalMessage<T> {
|
pub struct OriginalMessage<T, E> {
|
||||||
interaction_id: Id<InteractionMarker>,
|
interaction_id: Id<InteractionMarker>,
|
||||||
interaction_token: String,
|
interaction_token: String,
|
||||||
rt: Arc<Runtime<T>>,
|
rt: Arc<Runtime<T, E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> OriginalMessage<T>
|
impl<T, E> OriginalMessage<T, E>
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn edit(&self) -> OriginalMessageUpdater<T> {
|
pub fn edit(&self) -> OriginalMessageUpdater<T, E> {
|
||||||
OriginalMessageUpdater::new(
|
OriginalMessageUpdater::new(
|
||||||
self.interaction_id,
|
self.interaction_id,
|
||||||
self.interaction_token.clone(),
|
self.interaction_token.clone(),
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ pub enum InteractionError {
|
|||||||
|
|
||||||
#[error("No prefixes were returned for the event")]
|
#[error("No prefixes were returned for the event")]
|
||||||
PrefixesVectorEmpty,
|
PrefixesVectorEmpty,
|
||||||
|
|
||||||
|
#[error("The extras getter failed to retrieve extras")]
|
||||||
|
ExtrasGetterFailed,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use twilight_model::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::framework::{
|
use crate::framework::{
|
||||||
|
commands::extras::ExtrasGetter,
|
||||||
interactions::{InteractionError, context::InteractionContext},
|
interactions::{InteractionError, context::InteractionContext},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
};
|
};
|
||||||
@@ -14,13 +15,14 @@ use crate::framework::{
|
|||||||
use super::components::Component;
|
use super::components::Component;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ComponentRegistry<T> {
|
pub struct ComponentRegistry<T, E> {
|
||||||
inner: HashMap<String, Arc<dyn Component<T>>>,
|
inner: HashMap<String, Arc<dyn Component<T, E>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ComponentRegistry<T>
|
impl<T, E> ComponentRegistry<T, E>
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -28,7 +30,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(&self, id: impl ToString, component: Arc<dyn Component<T>>) {
|
pub fn register(&self, id: impl ToString, component: Arc<dyn Component<T, E>>) {
|
||||||
self.inner.upsert(id.to_string(), component);
|
self.inner.upsert(id.to_string(), component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +41,8 @@ where
|
|||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
event: &InteractionCreate,
|
event: &InteractionCreate,
|
||||||
rt: Arc<Runtime<T>>,
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
) -> Result<(), InteractionError> {
|
) -> Result<(), InteractionError> {
|
||||||
if let Some(data) = &event.data {
|
if let Some(data) = &event.data {
|
||||||
match data {
|
match data {
|
||||||
@@ -55,11 +58,23 @@ where
|
|||||||
.first()
|
.first()
|
||||||
.ok_or(InteractionError::PrefixesVectorEmpty)?;
|
.ok_or(InteractionError::PrefixesVectorEmpty)?;
|
||||||
|
|
||||||
|
let extras = extras
|
||||||
|
.get_extra(
|
||||||
|
&event.author().expect("Component interaction had no author"),
|
||||||
|
&rt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.inspect_err(|e| {
|
||||||
|
log::error!("Failed to get extras for component interaction: {e:?}")
|
||||||
|
})
|
||||||
|
.map_err(|_| InteractionError::ExtrasGetterFailed)?;
|
||||||
|
|
||||||
component
|
component
|
||||||
.execute(InteractionContext::new(
|
.execute(InteractionContext::new(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
rt.clone(),
|
rt.clone(),
|
||||||
prefix.to_string(),
|
prefix.to_string(),
|
||||||
|
extras,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use async_trait::async_trait;
|
|||||||
use twilight_model::gateway::payload::incoming::InteractionCreate;
|
use twilight_model::gateway::payload::incoming::InteractionCreate;
|
||||||
|
|
||||||
use crate::framework::{
|
use crate::framework::{
|
||||||
|
commands::extras::ExtrasGetter,
|
||||||
events::traits::{EventResult, OnInteractionHandler},
|
events::traits::{EventResult, OnInteractionHandler},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
};
|
};
|
||||||
@@ -12,11 +13,17 @@ use crate::framework::{
|
|||||||
pub struct InteractionRunner;
|
pub struct InteractionRunner;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T> OnInteractionHandler<T> for InteractionRunner
|
impl<T, E> OnInteractionHandler<T, E> for InteractionRunner
|
||||||
where
|
where
|
||||||
T: Send + Sync + 'static,
|
T: Send + Sync + 'static,
|
||||||
|
E: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
async fn on_interaction(&self, event: &InteractionCreate, rt: Arc<Runtime<T>>) -> EventResult {
|
async fn on_interaction(
|
||||||
Ok(rt.registry.execute(event, rt.clone()).await?)
|
&self,
|
||||||
|
event: &InteractionCreate,
|
||||||
|
rt: Arc<Runtime<T, E>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<T, Extras = E>>,
|
||||||
|
) -> EventResult {
|
||||||
|
Ok(rt.registry.execute(event, rt.clone(), extras).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::framework::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// The application's runtime tools.
|
/// The application's runtime tools.
|
||||||
pub struct Runtime<T> {
|
pub struct Runtime<T, E> {
|
||||||
/// The HTTP client used for making requests to the HTTP API.
|
/// The HTTP client used for making requests to the HTTP API.
|
||||||
pub http: Client,
|
pub http: Client,
|
||||||
|
|
||||||
@@ -36,13 +36,13 @@ pub struct Runtime<T> {
|
|||||||
pub app: Application,
|
pub app: Application,
|
||||||
|
|
||||||
/// The component registry.
|
/// The component registry.
|
||||||
pub registry: ComponentRegistry<T>,
|
pub registry: ComponentRegistry<T, E>,
|
||||||
|
|
||||||
/// The command prefixes getter.
|
/// The command prefixes getter.
|
||||||
pub prefixes: Arc<dyn PrefixGetter<T> + Send + Sync + 'static>,
|
pub prefixes: Arc<dyn PrefixGetter<T, E> + Send + Sync + 'static>,
|
||||||
|
|
||||||
/// The commands available in the application.
|
/// The commands available in the application.
|
||||||
pub commands: Vec<Arc<dyn Command<T>>>,
|
pub commands: Vec<Arc<dyn Command<T, E>>>,
|
||||||
|
|
||||||
/// The application state.
|
/// The application state.
|
||||||
pub state: Arc<T>,
|
pub state: Arc<T>,
|
||||||
@@ -50,7 +50,7 @@ pub struct Runtime<T> {
|
|||||||
pub cache: InMemoryCache,
|
pub cache: InMemoryCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Runtime<T> {
|
impl<T, E> Runtime<T, E> {
|
||||||
/// Get the command prefixes set for the server and user ID.
|
/// Get the command prefixes set for the server and user ID.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
@@ -74,7 +74,7 @@ impl<T> Runtime<T> {
|
|||||||
///
|
///
|
||||||
/// Returns:
|
/// Returns:
|
||||||
/// [`Option<Arc<dyn Command<T>>>`] - The command if found, otherwise `None`.
|
/// [`Option<Arc<dyn Command<T>>>`] - The command if found, otherwise `None`.
|
||||||
pub fn get_command(&self, name: &str) -> Option<Arc<dyn Command<T>>> {
|
pub fn get_command(&self, name: &str) -> Option<Arc<dyn Command<T, E>>> {
|
||||||
self.commands
|
self.commands
|
||||||
.iter()
|
.iter()
|
||||||
.find(|cmd| {
|
.find(|cmd| {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
extras::Extras,
|
||||||
framework::commands::{
|
framework::commands::{
|
||||||
Command, CommandError,
|
Command, CommandError,
|
||||||
arguments::{Argument, NeedyString, ParseArguments},
|
arguments::{Arg, Argument, NeedyString, ParseArguments},
|
||||||
context::CommandContext,
|
context::CommandContext,
|
||||||
},
|
},
|
||||||
groups::about::{
|
groups::about::{
|
||||||
@@ -19,7 +20,7 @@ use crate::{
|
|||||||
pub struct HelpCommand;
|
pub struct HelpCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for HelpCommand {
|
impl Command<State, Extras> for HelpCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"help"
|
"help"
|
||||||
}
|
}
|
||||||
@@ -38,12 +39,12 @@ impl Command<State> for HelpCommand {
|
|||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
args: &str,
|
args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let command_name: Option<NeedyString> = ParseArguments::parse(args).unwrap();
|
let command_name = Arg::<Option<NeedyString>>::parse(args, &ctx).await.unwrap();
|
||||||
|
|
||||||
if let Some(command_name) = command_name {
|
if let Some(command_name) = command_name.0 {
|
||||||
if let Some(command) = ctx.rt.get_command(&command_name) {
|
if let Some(command) = ctx.rt.get_command(&command_name) {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.embed(get_command_help_embed(&command, &ctx))
|
.embed(get_command_help_embed(&command, &ctx))
|
||||||
@@ -54,7 +55,7 @@ impl Command<State> for HelpCommand {
|
|||||||
.map_err(|_| CommandError::MessageSendingFailed)?;
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
} else {
|
} else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.embed(get_command_not_found_embed(ctx.rt, command_name.0))
|
.embed(get_command_not_found_embed(&ctx.rt, command_name.0))
|
||||||
.component(HelpGoBackButton, 1)
|
.component(HelpGoBackButton, 1)
|
||||||
.component(HelpSupportLinkButton, 1)
|
.component(HelpSupportLinkButton, 1)
|
||||||
.await
|
.await
|
||||||
@@ -62,7 +63,7 @@ impl Command<State> for HelpCommand {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.embed(get_all_command_help_embed(ctx.prefix, ctx.rt))
|
.embed(get_all_command_help_embed(ctx.prefix, &ctx.rt, &ctx.extras))
|
||||||
.component(HelpWebsiteLinkButton, 1)
|
.component(HelpWebsiteLinkButton, 1)
|
||||||
.component(HelpSupportLinkButton, 1)
|
.component(HelpSupportLinkButton, 1)
|
||||||
.component(HelpDonateLinkButton, 1)
|
.component(HelpDonateLinkButton, 1)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
extras::Extras,
|
||||||
framework::interactions::{InteractionError, components::Button, context::InteractionContext},
|
framework::interactions::{InteractionError, components::Button, context::InteractionContext},
|
||||||
groups::about::embeds::get_all_command_help_embed,
|
groups::about::embeds::get_all_command_help_embed,
|
||||||
state::State,
|
state::State,
|
||||||
@@ -10,19 +11,22 @@ use crate::{
|
|||||||
pub struct HelpGoBackButton;
|
pub struct HelpGoBackButton;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Button<State> for HelpGoBackButton {
|
impl Button<State, Extras> for HelpGoBackButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("← Go back to commands list")
|
Some("← Go back to commands list")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_press(&self, ctx: InteractionContext<State>) -> Result<(), InteractionError> {
|
async fn on_press(
|
||||||
|
&self,
|
||||||
|
ctx: InteractionContext<State, Extras>,
|
||||||
|
) -> Result<(), InteractionError> {
|
||||||
ctx
|
ctx
|
||||||
.original_message()
|
.original_message()
|
||||||
.expect(
|
.expect(
|
||||||
"Buttons always have the original message in the interaction context, this one didn't (got None)"
|
"Buttons always have the original message in the interaction context, this one didn't (got None)"
|
||||||
)
|
)
|
||||||
.edit()
|
.edit()
|
||||||
.embed(get_all_command_help_embed(&ctx.prefix, ctx.rt))
|
.embed(get_all_command_help_embed(&ctx.prefix, &ctx.rt, &ctx.extras))
|
||||||
.component(HelpWebsiteLinkButton, 1)
|
.component(HelpWebsiteLinkButton, 1)
|
||||||
.component(HelpSupportLinkButton, 1)
|
.component(HelpSupportLinkButton, 1)
|
||||||
.component(HelpDonateLinkButton, 1)
|
.component(HelpDonateLinkButton, 1)
|
||||||
@@ -41,7 +45,7 @@ impl Button<State> for HelpGoBackButton {
|
|||||||
pub struct HelpSupportLinkButton;
|
pub struct HelpSupportLinkButton;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Button<State> for HelpSupportLinkButton {
|
impl Button<State, Extras> for HelpSupportLinkButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Get help")
|
Some("Get help")
|
||||||
}
|
}
|
||||||
@@ -55,7 +59,7 @@ impl Button<State> for HelpSupportLinkButton {
|
|||||||
pub struct HelpWebsiteLinkButton;
|
pub struct HelpWebsiteLinkButton;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Button<State> for HelpWebsiteLinkButton {
|
impl Button<State, Extras> for HelpWebsiteLinkButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Website")
|
Some("Website")
|
||||||
}
|
}
|
||||||
@@ -69,7 +73,7 @@ impl Button<State> for HelpWebsiteLinkButton {
|
|||||||
pub struct HelpDonateLinkButton;
|
pub struct HelpDonateLinkButton;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Button<State> for HelpDonateLinkButton {
|
impl Button<State, Extras> for HelpDonateLinkButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Donate")
|
Some("Donate")
|
||||||
}
|
}
|
||||||
@@ -83,7 +87,7 @@ impl Button<State> for HelpDonateLinkButton {
|
|||||||
pub struct CodeOfConductLinkButton;
|
pub struct CodeOfConductLinkButton;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Button<State> for CodeOfConductLinkButton {
|
impl Button<State, Extras> for CodeOfConductLinkButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Code of Conduct")
|
Some("Code of Conduct")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use twilight_util::builder::embed::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
constants::{COLOR_RED, COLOR_WHITE},
|
constants::{COLOR_RED, COLOR_WHITE},
|
||||||
discord::{images, sanitizing::SanitizeDiscordMarkdown},
|
discord::{images, sanitizing::SanitizeDiscordMarkdown},
|
||||||
|
extras::Extras,
|
||||||
framework::{
|
framework::{
|
||||||
commands::{Command, context::CommandContext},
|
commands::{Command, context::CommandContext},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
@@ -17,8 +18,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn get_command_help_embed(
|
pub(super) fn get_command_help_embed(
|
||||||
command: &Arc<dyn Command<State>>,
|
command: &Arc<dyn Command<State, Extras>>,
|
||||||
ctx: &CommandContext<'_, State>,
|
ctx: &CommandContext<'_, State, Extras>,
|
||||||
) -> Embed {
|
) -> Embed {
|
||||||
let name = format!("{}{}", ctx.prefix, command.name());
|
let name = format!("{}{}", ctx.prefix, command.name());
|
||||||
let arguments = command
|
let arguments = command
|
||||||
@@ -79,7 +80,11 @@ pub(super) fn get_command_help_embed(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_all_command_help_embed<T>(prefix: &str, rt: Arc<Runtime<T>>) -> Embed {
|
pub(super) fn get_all_command_help_embed(
|
||||||
|
prefix: &str,
|
||||||
|
rt: &Runtime<State, Extras>,
|
||||||
|
extras: &Extras,
|
||||||
|
) -> Embed {
|
||||||
EmbedBuilder::new()
|
EmbedBuilder::new()
|
||||||
.author(EmbedAuthorBuilder::new(rt.app.name.clone()).icon_url(ImageSource::url(images::get_avatar_url(
|
.author(EmbedAuthorBuilder::new(rt.app.name.clone()).icon_url(ImageSource::url(images::get_avatar_url(
|
||||||
rt.app.bot.as_ref().unwrap().avatar,
|
rt.app.bot.as_ref().unwrap().avatar,
|
||||||
@@ -96,22 +101,24 @@ pub(super) fn get_all_command_help_embed<T>(prefix: &str, rt: Arc<Runtime<T>>) -
|
|||||||
))
|
))
|
||||||
.unwrap())
|
.unwrap())
|
||||||
.color(COLOR_WHITE)
|
.color(COLOR_WHITE)
|
||||||
.field(EmbedFieldBuilder::new("Commands", get_commands_table(rt)).build())
|
.field(EmbedFieldBuilder::new("Commands", get_commands_table(rt, extras)).build())
|
||||||
.footer(EmbedFooterBuilder::new(format!("v{} - Use {}help <command> to get more information about a specific command.", env!("CARGO_PKG_VERSION"), prefix)))
|
.footer(EmbedFooterBuilder::new(format!("v{} - Use {}help <command> to get more information about a specific command.", env!("CARGO_PKG_VERSION"), prefix)))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_commands_table<T>(rt: Arc<Runtime<T>>) -> String {
|
pub(super) fn get_commands_table<T>(rt: &Runtime<T, Extras>, extras: &Extras) -> String {
|
||||||
let mut table = String::new();
|
let mut table = String::new();
|
||||||
|
|
||||||
for command in rt.commands.iter() {
|
for command in rt.commands.iter() {
|
||||||
table.push_str(&format!("{:<12}", command.name()));
|
if command.show_in_help(rt, extras) {
|
||||||
|
table.push_str(&format!("{:<13}", command.name()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format!("```\n{table}\n```")
|
format!("```\n{table}\n```")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_command_not_found_embed<T>(rt: Arc<Runtime<T>>, name: String) -> Embed {
|
pub(super) fn get_command_not_found_embed<T>(rt: &Runtime<T, Extras>, name: String) -> Embed {
|
||||||
EmbedBuilder::new()
|
EmbedBuilder::new()
|
||||||
.color(COLOR_RED)
|
.color(COLOR_RED)
|
||||||
.author(EmbedAuthorBuilder::new(rt.app.name.clone()).icon_url(ImageSource::url(images::get_avatar_url(
|
.author(EmbedAuthorBuilder::new(rt.app.name.clone()).icon_url(ImageSource::url(images::get_avatar_url(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use twilight_model::id::Id;
|
|||||||
use crate::{
|
use crate::{
|
||||||
database::models::{Call, Channel},
|
database::models::{Call, Channel},
|
||||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||||
|
extras::Extras,
|
||||||
framework::commands::{
|
framework::commands::{
|
||||||
Command, CommandError, context::CommandContext, permissions::Permission,
|
Command, CommandError, context::CommandContext, permissions::Permission,
|
||||||
},
|
},
|
||||||
@@ -39,7 +40,7 @@ impl CallCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for CallCommand {
|
impl Command<State, Extras> for CallCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"call"
|
"call"
|
||||||
}
|
}
|
||||||
@@ -48,13 +49,13 @@ impl Command<State> for CallCommand {
|
|||||||
&["c"]
|
&["c"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
vec![Box::new(NotBannedPermission)]
|
vec![Box::new(NotBannedPermission)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
_args: &str,
|
_args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let channel = Channel::get_or_create_by_id(
|
let channel = Channel::get_or_create_by_id(
|
||||||
@@ -66,7 +67,7 @@ impl Command<State> for CallCommand {
|
|||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
log::error!("Failed to get or create channel: {e:?}");
|
log::error!("Failed to get or create channel: {e:?}");
|
||||||
})
|
})
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
if channel.is_searching || self.channel_lock.contains(&ctx.event.channel_id.get()) {
|
if channel.is_searching || self.channel_lock.contains(&ctx.event.channel_id.get()) {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
@@ -100,7 +101,7 @@ impl Command<State> for CallCommand {
|
|||||||
log::error!("Failed to start call: {e:?}");
|
log::error!("Failed to start call: {e:?}");
|
||||||
self.channel_lock.remove(&ctx.event.channel_id.get());
|
self.channel_lock.remove(&ctx.event.channel_id.get());
|
||||||
})
|
})
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
self.channel_lock
|
self.channel_lock
|
||||||
.remove_async(&ctx.event.channel_id.get())
|
.remove_async(&ctx.event.channel_id.get())
|
||||||
@@ -141,7 +142,7 @@ impl Command<State> for CallCommand {
|
|||||||
pub struct SkipCommand;
|
pub struct SkipCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for SkipCommand {
|
impl Command<State, Extras> for SkipCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"skip"
|
"skip"
|
||||||
}
|
}
|
||||||
@@ -150,13 +151,13 @@ impl Command<State> for SkipCommand {
|
|||||||
&["s", "next"]
|
&["s", "next"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
vec![Box::new(NotBannedPermission)]
|
vec![Box::new(NotBannedPermission)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
_args: &str,
|
_args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let channel = Channel::get_or_create_by_id(
|
let channel = Channel::get_or_create_by_id(
|
||||||
@@ -168,7 +169,7 @@ impl Command<State> for SkipCommand {
|
|||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
log::error!("Failed to get or create channel: {e:?}");
|
log::error!("Failed to get or create channel: {e:?}");
|
||||||
})
|
})
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
if channel.call_id.is_none() {
|
if channel.call_id.is_none() {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
@@ -197,13 +198,13 @@ impl Command<State> for SkipCommand {
|
|||||||
let call = Call::get_by_id(channel.call_id.unwrap(), &ctx.rt.state.db)
|
let call = Call::get_by_id(channel.call_id.unwrap(), &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
.map_err(|_| CommandError::DatabaseFailed)?
|
||||||
.ok_or(CommandError::DatabaseInconsistentState)?;
|
.ok_or(CommandError::DatabaseInconsistentState)?;
|
||||||
|
|
||||||
call.hangup(&ctx.rt.state.db)
|
call.hangup(&ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
let other_channel_id = call.other_party_channel_id(ctx.event.channel_id.get());
|
let other_channel_id = call.other_party_channel_id(ctx.event.channel_id.get());
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ impl Command<State> for SkipCommand {
|
|||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
log::error!("Failed to start (skipped) call: {e:?}");
|
log::error!("Failed to start (skipped) call: {e:?}");
|
||||||
})
|
})
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
if let Some(call) = call {
|
if let Some(call) = call {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
@@ -259,7 +260,7 @@ impl Command<State> for SkipCommand {
|
|||||||
pub struct HangupCommand;
|
pub struct HangupCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for HangupCommand {
|
impl Command<State, Extras> for HangupCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"hangup"
|
"hangup"
|
||||||
}
|
}
|
||||||
@@ -268,19 +269,19 @@ impl Command<State> for HangupCommand {
|
|||||||
&["h", "hang"]
|
&["h", "hang"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
vec![Box::new(NotBannedPermission)]
|
vec![Box::new(NotBannedPermission)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
_args: &str,
|
_args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let Some(channel) = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
let Some(channel) = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
.map_err(|_| CommandError::DatabaseFailed)?
|
||||||
else {
|
else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
@@ -300,7 +301,7 @@ impl Command<State> for HangupCommand {
|
|||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
log::error!("Failed to cancel search: {e:?}");
|
log::error!("Failed to cancel search: {e:?}");
|
||||||
})
|
})
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content(format!("☎️ {}", get_string_call_no_answer(ctx.prefix)))
|
.content(format!("☎️ {}", get_string_call_no_answer(ctx.prefix)))
|
||||||
@@ -314,7 +315,7 @@ impl Command<State> for HangupCommand {
|
|||||||
let Some(call) = Call::get_by_id(call_id, &ctx.rt.state.db)
|
let Some(call) = Call::get_by_id(call_id, &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
.map_err(|_| CommandError::DatabaseFailed)?
|
||||||
else {
|
else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content("❌ Call not found.")
|
.content("❌ Call not found.")
|
||||||
@@ -327,7 +328,7 @@ impl Command<State> for HangupCommand {
|
|||||||
call.hangup(&ctx.rt.state.db)
|
call.hangup(&ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
@@ -366,7 +367,7 @@ impl Command<State> for HangupCommand {
|
|||||||
pub struct FriendRequestCommand;
|
pub struct FriendRequestCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for FriendRequestCommand {
|
impl Command<State, Extras> for FriendRequestCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"friendreq"
|
"friendreq"
|
||||||
}
|
}
|
||||||
@@ -379,19 +380,19 @@ impl Command<State> for FriendRequestCommand {
|
|||||||
"Shows your username to the other party and asks them to send you a friend request on Discord."
|
"Shows your username to the other party and asks them to send you a friend request on Discord."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
vec![Box::new(NotBannedPermission)]
|
vec![Box::new(NotBannedPermission)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
_args: &str,
|
_args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
let Some(channel) = channel else {
|
let Some(channel) = channel else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
@@ -409,7 +410,7 @@ impl Command<State> for FriendRequestCommand {
|
|||||||
Call::get_by_id(call_id, &ctx.rt.state.db)
|
Call::get_by_id(call_id, &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
.map_err(|_| CommandError::DatabaseFailed)?
|
||||||
} else {
|
} else {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{framework::interactions::components::Button, state::State};
|
use crate::{extras::Extras, framework::interactions::components::Button, state::State};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CodeOfConductButton;
|
pub struct CodeOfConductButton;
|
||||||
|
|
||||||
impl Button<State> for CodeOfConductButton {
|
impl Button<State, Extras> for CodeOfConductButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Code of Conduct")
|
Some("Code of Conduct")
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ impl Button<State> for CodeOfConductButton {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NeedHelpButton;
|
pub struct NeedHelpButton;
|
||||||
|
|
||||||
impl Button<State> for NeedHelpButton {
|
impl Button<State, Extras> for NeedHelpButton {
|
||||||
fn label(&self) -> Option<&'static str> {
|
fn label(&self) -> Option<&'static str> {
|
||||||
Some("Need Help?")
|
Some("Need Help?")
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-17
@@ -11,21 +11,18 @@ use twilight_util::builder::embed::{EmbedAuthorBuilder, EmbedBuilder, ImageSourc
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::COLOR_WHITE,
|
constants::COLOR_WHITE,
|
||||||
database::models::{
|
database::models::{Call, Channel, Message, User, UserIncident},
|
||||||
Call, Channel, Message, User, UserIncident, get_or_create_user_and_set_last_seen,
|
|
||||||
},
|
|
||||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||||
|
extras::Extras,
|
||||||
framework::{
|
framework::{
|
||||||
events::traits::{
|
commands::extras::ExtrasGetter, events::traits::{
|
||||||
EventResult, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
|
EventResult, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
|
||||||
},
|
}, interactions::components::ActionRow, runtime::Runtime
|
||||||
interactions::components::ActionRow,
|
|
||||||
runtime::Runtime,
|
|
||||||
},
|
},
|
||||||
groups::calls::{
|
groups::calls::{
|
||||||
components::{CodeOfConductButton, NeedHelpButton},
|
components::{CodeOfConductButton, NeedHelpButton},
|
||||||
embeds, get_display_profile,
|
embeds, get_display_profile,
|
||||||
moderation::{Action, Rule, escape_message},
|
moderation::{escape_message, Action, Rule},
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
utils::{ellipse, i64_to_u64},
|
utils::{ellipse, i64_to_u64},
|
||||||
@@ -35,7 +32,7 @@ async fn penalize_user(
|
|||||||
user: User,
|
user: User,
|
||||||
message: Message,
|
message: Message,
|
||||||
broken_rules: Vec<Arc<dyn Rule>>,
|
broken_rules: Vec<Arc<dyn Rule>>,
|
||||||
rt: &Runtime<State>,
|
rt: &Runtime<State, Extras>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let reason = format!(
|
let reason = format!(
|
||||||
"You broke the following rules during a call:\n{}",
|
"You broke the following rules during a call:\n{}",
|
||||||
@@ -88,8 +85,13 @@ async fn penalize_user(
|
|||||||
pub struct CallMessageHandler;
|
pub struct CallMessageHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnMessageHandler<State> for CallMessageHandler {
|
impl OnMessageHandler<State, Extras> for CallMessageHandler {
|
||||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_message(
|
||||||
|
&self,
|
||||||
|
event: &MessageCreate,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<State, Extras = Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
if event.author.bot || event.author.system.unwrap_or(false) {
|
if event.author.bot || event.author.system.unwrap_or(false) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -144,7 +146,8 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = get_or_create_user_and_set_last_seen(event.author.clone(), &rt.state.db).await?;
|
let user =
|
||||||
|
User::get_or_create_and_set_last_seen(event.author.clone(), &rt.state.db).await?;
|
||||||
|
|
||||||
let user_incidents = UserIncident::get_for_user(event.author.id.get(), &rt.state.db)
|
let user_incidents = UserIncident::get_for_user(event.author.id.get(), &rt.state.db)
|
||||||
.await?
|
.await?
|
||||||
@@ -314,8 +317,12 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
|||||||
pub struct CallMessageEditHandler;
|
pub struct CallMessageEditHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
impl OnMessageEditHandler<State, Extras> for CallMessageEditHandler {
|
||||||
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_message_edit(
|
||||||
|
&self,
|
||||||
|
event: &MessageUpdate,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"Edited message was '{}' sent by {} (bot? {:?})",
|
"Edited message was '{}' sent by {} (bot? {:?})",
|
||||||
event.content,
|
event.content,
|
||||||
@@ -368,7 +375,9 @@ impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
|||||||
.reply(event.id)
|
.reply(event.id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user = get_or_create_user_and_set_last_seen(event.author.clone(), &rt.state.db).await?;
|
let user =
|
||||||
|
User::get_or_create_and_set_last_seen(event.author.clone(), &rt.state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
penalize_user(user, message, rules, &rt).await?;
|
penalize_user(user, message, rules, &rt).await?;
|
||||||
|
|
||||||
@@ -404,11 +413,11 @@ impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
|||||||
pub struct CallMessageDeleteHandler;
|
pub struct CallMessageDeleteHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnMessageDeleteHandler<State> for CallMessageDeleteHandler {
|
impl OnMessageDeleteHandler<State, Extras> for CallMessageDeleteHandler {
|
||||||
async fn on_message_delete(
|
async fn on_message_delete(
|
||||||
&self,
|
&self,
|
||||||
event: &MessageDelete,
|
event: &MessageDelete,
|
||||||
rt: Arc<Runtime<State>>,
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
) -> EventResult {
|
) -> EventResult {
|
||||||
let Some(channel) = Channel::get_by_id(event.channel_id.get(), &rt.state.db).await? else {
|
let Some(channel) = Channel::get_by_id(event.channel_id.get(), &rt.state.db).await? else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ gooncream
|
|||||||
gooncreaming
|
gooncreaming
|
||||||
goonbud
|
goonbud
|
||||||
goonbuds
|
goonbuds
|
||||||
|
goonete
|
||||||
|
goonetes
|
||||||
boty hole
|
boty hole
|
||||||
suck my pp
|
suck my pp
|
||||||
ass cracker
|
ass cracker
|
||||||
@@ -98,7 +100,7 @@ masterbaiting
|
|||||||
masturbatigng
|
masturbatigng
|
||||||
horny
|
horny
|
||||||
hrny
|
hrny
|
||||||
whore
|
whore / who re
|
||||||
whre
|
whre
|
||||||
tickle your pickle
|
tickle your pickle
|
||||||
tickle yr pickle
|
tickle yr pickle
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use async_trait::async_trait;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::UserIncident,
|
database::models::UserIncident,
|
||||||
|
extras::Extras,
|
||||||
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
||||||
groups::calls::components::{CodeOfConductButton, NeedHelpButton},
|
groups::calls::components::{CodeOfConductButton, NeedHelpButton},
|
||||||
state::State,
|
state::State,
|
||||||
@@ -11,12 +12,12 @@ use crate::{
|
|||||||
pub struct NotBannedPermission;
|
pub struct NotBannedPermission;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Permission<State> for NotBannedPermission {
|
impl Permission<State, Extras> for NotBannedPermission {
|
||||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||||
let incidents = UserIncident::get_for_user(ctx.event.author.id.get(), &ctx.rt.state.db)
|
let incidents = UserIncident::get_for_user(ctx.event.author.id.get(), &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get user incidents: {e}"))
|
.inspect_err(|e| log::error!("Failed to get user incidents: {e}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
if incidents.iter().any(|i| i.is_active()) {
|
if incidents.iter().any(|i| i.is_active()) {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@@ -25,7 +26,10 @@ impl Permission<State> for NotBannedPermission {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_handle_denied(&self, ctx: &CommandContext<State>) -> Result<(), CommandError> {
|
async fn on_handle_denied(
|
||||||
|
&self,
|
||||||
|
ctx: &CommandContext<State, Extras>,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content("❌ You've been banned from Payphone.")
|
.content("❌ You've been banned from Payphone.")
|
||||||
.component(CodeOfConductButton, 1)
|
.component(CodeOfConductButton, 1)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use async_trait::async_trait;
|
|||||||
use twilight_model::gateway::payload::incoming::Ready;
|
use twilight_model::gateway::payload::incoming::Ready;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
extras::Extras,
|
||||||
framework::{
|
framework::{
|
||||||
events::traits::{EventResult, OnReadyHandler, OnReconnectHandler},
|
events::traits::{EventResult, OnReadyHandler, OnReconnectHandler},
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
@@ -18,8 +19,8 @@ use crate::{
|
|||||||
pub struct ReadyHandler;
|
pub struct ReadyHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnReadyHandler<State> for ReadyHandler {
|
impl OnReadyHandler<State, Extras> for ReadyHandler {
|
||||||
async fn on_ready(&self, event: &Ready, _: Arc<Runtime<State>>) -> EventResult {
|
async fn on_ready(&self, event: &Ready, _: Arc<Runtime<State, Extras>>) -> EventResult {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Ready! Logged in as {}#{:>04}",
|
"Ready! Logged in as {}#{:>04}",
|
||||||
event.user.name,
|
event.user.name,
|
||||||
@@ -35,8 +36,8 @@ impl OnReadyHandler<State> for ReadyHandler {
|
|||||||
pub struct ReconnectHandler;
|
pub struct ReconnectHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnReconnectHandler<State> for ReconnectHandler {
|
impl OnReconnectHandler<State, Extras> for ReconnectHandler {
|
||||||
async fn on_reconnect(&self, _: Arc<Runtime<State>>) -> EventResult {
|
async fn on_reconnect(&self, _: Arc<Runtime<State, Extras>>) -> EventResult {
|
||||||
log::info!("The gateway asked for a reconnection");
|
log::info!("The gateway asked for a reconnection");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -6,19 +6,14 @@ use twilight_util::builder::embed::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::COLOR_WHITE,
|
constants::COLOR_WHITE, database::models::User, discord::{images, sanitizing::SanitizeDiscordMarkdown}, extras::Extras, framework::commands::{context::CommandContext, Command, CommandError}, groups::leveling::constants::KEY_LEADERBOARD_MONTHLY, state::State
|
||||||
database::models::User,
|
|
||||||
discord::{images, sanitizing::SanitizeDiscordMarkdown},
|
|
||||||
framework::commands::{Command, CommandError, context::CommandContext},
|
|
||||||
groups::leveling::constants::KEY_LEADERBOARD_MONTHLY,
|
|
||||||
state::State,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LeaderboardCommand;
|
pub struct LeaderboardCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for LeaderboardCommand {
|
impl Command<State, Extras> for LeaderboardCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"leaderboard"
|
"leaderboard"
|
||||||
}
|
}
|
||||||
@@ -33,7 +28,7 @@ impl Command<State> for LeaderboardCommand {
|
|||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
_args: &str,
|
_args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let mut redis = ctx
|
let mut redis = ctx
|
||||||
@@ -61,7 +56,7 @@ impl Command<State> for LeaderboardCommand {
|
|||||||
for (user_id, score) in top {
|
for (user_id, score) in top {
|
||||||
let Some(user) = User::get_by_id(user_id, &ctx.rt.state.db)
|
let Some(user) = User::get_by_id(user_id, &ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
.map_err(|_| CommandError::DatabaseFailed)?
|
||||||
else {
|
else {
|
||||||
log::trace!("User ID {user_id} not found for leaderboard");
|
log::trace!("User ID {user_id} not found for leaderboard");
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ use time::{Date, Month, OffsetDateTime, Time};
|
|||||||
use twilight_model::gateway::payload::incoming::MessageCreate;
|
use twilight_model::gateway::payload::incoming::MessageCreate;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::Channel, framework::{
|
database::models::Channel,
|
||||||
events::traits::{EventResult, OnMessageHandler},
|
extras::Extras,
|
||||||
runtime::Runtime,
|
framework::{
|
||||||
}, groups::leveling::constants::KEY_LEADERBOARD_MONTHLY, state::State
|
commands::extras::ExtrasGetter, events::traits::{EventResult, OnMessageHandler}, runtime::Runtime
|
||||||
|
},
|
||||||
|
groups::leveling::constants::KEY_LEADERBOARD_MONTHLY,
|
||||||
|
state::State,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn seconds_until_end_of_month() -> i64 {
|
fn seconds_until_end_of_month() -> i64 {
|
||||||
@@ -38,8 +41,13 @@ fn seconds_until_end_of_month() -> i64 {
|
|||||||
pub struct MessageLevelingHandler;
|
pub struct MessageLevelingHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnMessageHandler<State> for MessageLevelingHandler {
|
impl OnMessageHandler<State, Extras> for MessageLevelingHandler {
|
||||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_message(
|
||||||
|
&self,
|
||||||
|
event: &MessageCreate,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
extras: Arc<dyn ExtrasGetter<State, Extras = Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
if event.author.bot || event.author.system.unwrap_or(false) {
|
if event.author.bot || event.author.system.unwrap_or(false) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -72,7 +80,9 @@ impl OnMessageHandler<State> for MessageLevelingHandler {
|
|||||||
let lock = redis.ttl(&key).await?;
|
let lock = redis.ttl(&key).await?;
|
||||||
|
|
||||||
if let IntegerReplyOrNoOp::IntegerReply(seconds) = lock {
|
if let IntegerReplyOrNoOp::IntegerReply(seconds) = lock {
|
||||||
log::trace!("No XP for the message because the user is already in the cooldown period. Remaining seconds: {seconds}");
|
log::trace!(
|
||||||
|
"No XP for the message because the user is already in the cooldown period. Remaining seconds: {seconds}"
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +102,9 @@ impl OnMessageHandler<State> for MessageLevelingHandler {
|
|||||||
let leaderboard_exat = redis.ttl(KEY_LEADERBOARD_MONTHLY).await?;
|
let leaderboard_exat = redis.ttl(KEY_LEADERBOARD_MONTHLY).await?;
|
||||||
|
|
||||||
if leaderboard_exat == IntegerReplyOrNoOp::ExistsButNotRelevant {
|
if leaderboard_exat == IntegerReplyOrNoOp::ExistsButNotRelevant {
|
||||||
redis.expire(KEY_LEADERBOARD_MONTHLY, seconds_until_end_of_month()).await?;
|
redis
|
||||||
|
.expire(KEY_LEADERBOARD_MONTHLY, seconds_until_end_of_month())
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use scylla::client::session::Session;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
database::models::{Message, User},
|
||||||
|
extras::Extras,
|
||||||
|
framework::commands::{
|
||||||
|
arguments::{ArgumentParsingError, FromArguments, NeedyString},
|
||||||
|
context::CommandContext,
|
||||||
|
},
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Fetch a user by their ID from the database.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `id` - The user's ID.
|
||||||
|
/// * `db` - The database session.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`Result<User, ArgumentParser>`] - The user from the DB, or an [`ArgumentParsingError`] ready
|
||||||
|
/// to be returned by the parser.
|
||||||
|
async fn get_user_by_id(id: u64, db: &Session) -> Result<User, ArgumentParsingError> {
|
||||||
|
let Some(user) = User::get_by_id(id, db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ArgumentParsingError::DatabaseFailed)?
|
||||||
|
else {
|
||||||
|
return Err(ArgumentParsingError::InvalidArgument);
|
||||||
|
};
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches the user from the message's reply and falls back to the command executor.
|
||||||
|
///
|
||||||
|
/// This function takes into account messages on call and traces them back to the original author.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// * `ctx` - The command context.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// [`Result<User, ArgumentParsingError>`] - The user from the message's reply, or an
|
||||||
|
/// [`ArgumentParsingError`] ready to be returned by the parser.
|
||||||
|
async fn user_from_reply_or_command_executor(
|
||||||
|
ctx: &CommandContext<'_, State, Extras>,
|
||||||
|
) -> Result<User, ArgumentParsingError> {
|
||||||
|
if let Some(referenced) = &ctx.event.referenced_message {
|
||||||
|
if !referenced.author.system.unwrap_or(false) {
|
||||||
|
if referenced.author.bot {
|
||||||
|
if let Some(message) =
|
||||||
|
Message::get_by_propagation_message_id(referenced.id.get(), &ctx.rt.state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ArgumentParsingError::DatabaseFailed)?
|
||||||
|
{
|
||||||
|
return get_user_by_id(message.author_id, &ctx.rt.state.db).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the replied message's author is not a bot or system user, use them.
|
||||||
|
return get_user_by_id(referenced.author.id.get(), &ctx.rt.state.db).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to command executor
|
||||||
|
get_user_by_id(ctx.event.author.id.get(), &ctx.rt.state.db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a user mention or ID into a UserArgument.
|
||||||
|
///
|
||||||
|
/// It parses from multiple sources, attempting in the following way:
|
||||||
|
///
|
||||||
|
/// 1. If the argument is a Discord mention, parsing it.
|
||||||
|
/// 2. If the argument is a Discord ID, finding it in the DB.
|
||||||
|
/// 3. If the argument is a Discord mention, finding it in the DB.
|
||||||
|
/// 4. If there's no argument and the message replies to another message:
|
||||||
|
/// a. If the replied message's author is a bot, attempts to query the user who sent it from the
|
||||||
|
/// other side of the call. If it fails, the command executor is used.
|
||||||
|
/// b. If the replied message's author is not a bot, they're used.
|
||||||
|
/// 5. If there's no argument and the message is not replying to any message, the command executor
|
||||||
|
/// is used.
|
||||||
|
pub struct UserArgument(pub User);
|
||||||
|
|
||||||
|
impl FromArguments<State, Extras> for UserArgument {
|
||||||
|
async fn from_args<'a>(
|
||||||
|
args: &'a str,
|
||||||
|
ctx: &'a CommandContext<'_, State, Extras>,
|
||||||
|
) -> Result<(Self, &'a str), ArgumentParsingError> {
|
||||||
|
let user: User;
|
||||||
|
|
||||||
|
if !args.is_empty() {
|
||||||
|
// There's an argument, so that argument is used to find the user.
|
||||||
|
|
||||||
|
let (identifier, args) = NeedyString::from_args(args, ctx).await?;
|
||||||
|
|
||||||
|
if identifier.starts_with("<@") && identifier.ends_with(">") {
|
||||||
|
// The argument is a Discord mention.
|
||||||
|
|
||||||
|
let user_id: u64 = identifier[2..identifier.len() - 1]
|
||||||
|
.to_string()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ArgumentParsingError::InvalidArgument)?;
|
||||||
|
|
||||||
|
user = get_user_by_id(user_id, &ctx.rt.state.db).await?;
|
||||||
|
} else if identifier.chars().all(|c| c.is_ascii_digit()) {
|
||||||
|
// The argument is a user ID.
|
||||||
|
|
||||||
|
let user_id: u64 = identifier
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ArgumentParsingError::InvalidArgument)?;
|
||||||
|
|
||||||
|
user = get_user_by_id(user_id, &ctx.rt.state.db).await?;
|
||||||
|
} else {
|
||||||
|
// The argument is (probably) a username.
|
||||||
|
|
||||||
|
let Some(db_user) = User::get_by_name(identifier.0, &ctx.rt.state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ArgumentParsingError::DatabaseFailed)?
|
||||||
|
else {
|
||||||
|
// Check in the message's reply, and if not use the command executor.
|
||||||
|
user = user_from_reply_or_command_executor(ctx).await?;
|
||||||
|
|
||||||
|
return Ok((UserArgument(user), args));
|
||||||
|
};
|
||||||
|
|
||||||
|
user = db_user;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check in the message's reply, and if not use the command executor.
|
||||||
|
user = user_from_reply_or_command_executor(ctx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((UserArgument(user), args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for UserArgument {
|
||||||
|
type Target = User;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for UserArgument {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
database::models::UserIncident,
|
||||||
|
extras::Extras,
|
||||||
|
framework::{
|
||||||
|
commands::{
|
||||||
|
Command, CommandError,
|
||||||
|
arguments::{Arg, Argument, ArgumentParsingError, ParseArguments},
|
||||||
|
context::CommandContext,
|
||||||
|
permissions::Permission,
|
||||||
|
},
|
||||||
|
runtime::Runtime,
|
||||||
|
},
|
||||||
|
groups::moderation::{
|
||||||
|
arguments::UserArgument,
|
||||||
|
permissions::{BotModeratorPermission, BotOwnerPermission},
|
||||||
|
},
|
||||||
|
state::State,
|
||||||
|
utils::Bye,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ModeratorCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Command<State, Extras> for ModeratorCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"moderator"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aliases(&self) -> &'static [&'static str] {
|
||||||
|
&["mod"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
|
vec![Box::new(BotOwnerPermission)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_in_help(&self, rt: &Runtime<State, Extras>, extras: &Extras) -> bool {
|
||||||
|
extras.user.id == rt.state.bot_owner_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arguments(&self) -> &'static [Argument] {
|
||||||
|
&[Argument {
|
||||||
|
name: "user",
|
||||||
|
is_optional: false,
|
||||||
|
is_greedy: false,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
|
args: &str,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
|
let Arg(mut user) = match Arg::<UserArgument>::parse(args, &ctx).await {
|
||||||
|
Ok(arg) => arg,
|
||||||
|
Err(e) => match e {
|
||||||
|
ArgumentParsingError::InvalidArgument => {
|
||||||
|
ctx.send()
|
||||||
|
.content("❌ User not found in DB.")
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
ArgumentParsingError::DatabaseFailed => {
|
||||||
|
return Err(CommandError::DatabaseFailed);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
unreachable!("Unexpected error: {e}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
user.is_moderator = !user.is_moderator;
|
||||||
|
|
||||||
|
user.insert(&ctx.rt.state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
|
ctx.send()
|
||||||
|
.content(format!(
|
||||||
|
"👍 Done. <@{}> {}",
|
||||||
|
user.id,
|
||||||
|
if user.is_moderator {
|
||||||
|
"**is now** a moderator"
|
||||||
|
} else {
|
||||||
|
"**is no longer** a moderator"
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IncidentsCommand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Command<State, Extras> for IncidentsCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"incidents"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aliases(&self) -> &'static [&'static str] {
|
||||||
|
&["i"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
|
vec![Box::new(BotModeratorPermission)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_in_help(&self, _rt: &Runtime<State, Extras>, extras: &Extras) -> bool {
|
||||||
|
extras.user.is_moderator
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
|
args: &str,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
|
let user = match Arg::<UserArgument>::parse(args, &ctx).await {
|
||||||
|
Ok(Arg(user)) => user,
|
||||||
|
Err(e) => match e {
|
||||||
|
ArgumentParsingError::InvalidArgument => {
|
||||||
|
ctx.send()
|
||||||
|
.content("❌ User not found in DB.")
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
ArgumentParsingError::DatabaseFailed => {
|
||||||
|
return Err(CommandError::DatabaseFailed);
|
||||||
|
}
|
||||||
|
e => {
|
||||||
|
unreachable!("Unexpected error: {e}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let incidents = UserIncident::get_for_user(user.id, &ctx.rt.state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
|
ctx.send()
|
||||||
|
.content(format!(
|
||||||
|
"Great! The user fetched from the DB is {}",
|
||||||
|
user.name
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.bye();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod arguments;
|
||||||
|
pub mod commands;
|
||||||
|
pub mod permissions;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
extras::Extras,
|
||||||
|
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BotOwnerPermission;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Permission<State, Extras> for BotOwnerPermission {
|
||||||
|
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||||
|
Ok(ctx.extras.user.id == ctx.rt.state.bot_owner_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_handle_denied(
|
||||||
|
&self,
|
||||||
|
ctx: &CommandContext<State, Extras>,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
|
ctx.send()
|
||||||
|
.content("⛔ Not enough aura to run this command.")
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BotModeratorPermission;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Permission<State, Extras> for BotModeratorPermission {
|
||||||
|
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||||
|
Ok(ctx.extras.user.is_moderator)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_handle_denied(
|
||||||
|
&self,
|
||||||
|
ctx: &CommandContext<State, Extras>,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
|
ctx.send()
|
||||||
|
.content("⛔ You're not a Payphone moderator.\n-# If you need to check your incidents, contact a Payphone moderator in the official server.")
|
||||||
|
.await
|
||||||
|
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,16 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::DEFAULT_PREFIX,
|
constants::DEFAULT_PREFIX, database::models::Server, discord::sanitizing::SanitizeDiscordMarkdown, extras::Extras, framework::commands::{
|
||||||
database::models::Server,
|
arguments::{Arg, Argument, GreedyString, ParseArguments}, context::CommandContext, permissions::Permission, Command, CommandError
|
||||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
}, groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission}, state::State
|
||||||
framework::commands::{
|
|
||||||
Command, CommandError,
|
|
||||||
arguments::{Argument, GreedyString, ParseArguments},
|
|
||||||
context::CommandContext,
|
|
||||||
permissions::Permission,
|
|
||||||
},
|
|
||||||
groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission},
|
|
||||||
state::State,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PrefixCommand;
|
pub struct PrefixCommand;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Command<State> for PrefixCommand {
|
impl Command<State, Extras> for PrefixCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"prefix"
|
"prefix"
|
||||||
}
|
}
|
||||||
@@ -39,7 +31,7 @@ impl Command<State> for PrefixCommand {
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn permissions(&self) -> Vec<Box<dyn Permission<State> + Send + Sync + 'static>> {
|
fn permissions(&self) -> Vec<Box<dyn Permission<State, Extras> + Send + Sync + 'static>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(GuildOnlyPermission),
|
Box::new(GuildOnlyPermission),
|
||||||
Box::new(ServerModeratorPermission),
|
Box::new(ServerModeratorPermission),
|
||||||
@@ -48,10 +40,10 @@ impl Command<State> for PrefixCommand {
|
|||||||
|
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
ctx: CommandContext<'_, State>,
|
ctx: CommandContext<'_, State, Extras>,
|
||||||
args: &str,
|
args: &str,
|
||||||
) -> Result<(), CommandError> {
|
) -> Result<(), CommandError> {
|
||||||
let prefix: GreedyString = ParseArguments::parse(args).unwrap();
|
let Arg(prefix) = Arg::<GreedyString>::parse(args, &ctx).await.unwrap();
|
||||||
let mut prefix = prefix.trim();
|
let mut prefix = prefix.trim();
|
||||||
|
|
||||||
let mut server = Server::get_or_create_by_id(
|
let mut server = Server::get_or_create_by_id(
|
||||||
@@ -64,7 +56,7 @@ impl Command<State> for PrefixCommand {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| log::error!("Failed to get or create server: {e}"))
|
.inspect_err(|e| log::error!("Failed to get or create server: {e}"))
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
if prefix.is_empty() {
|
if prefix.is_empty() {
|
||||||
prefix = DEFAULT_PREFIX;
|
prefix = DEFAULT_PREFIX;
|
||||||
@@ -74,7 +66,7 @@ impl Command<State> for PrefixCommand {
|
|||||||
server
|
server
|
||||||
.upsert(&ctx.rt.state.db)
|
.upsert(&ctx.rt.state.db)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||||
|
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content(format!(
|
.content(format!(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use twilight_model::gateway::payload::incoming::{GuildCreate, GuildDelete, Guild
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::models::Server,
|
database::models::Server,
|
||||||
|
extras::Extras,
|
||||||
framework::{
|
framework::{
|
||||||
events::traits::{
|
events::traits::{
|
||||||
EventResult, OnServerCreateHandler, OnServerDeleteHandler, OnServerUpdateHandler,
|
EventResult, OnServerCreateHandler, OnServerDeleteHandler, OnServerUpdateHandler,
|
||||||
@@ -21,8 +22,12 @@ use crate::{
|
|||||||
pub struct ServerCreateHandler;
|
pub struct ServerCreateHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnServerCreateHandler<State> for ServerCreateHandler {
|
impl OnServerCreateHandler<State, Extras> for ServerCreateHandler {
|
||||||
async fn on_server_create(&self, event: &GuildCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_server_create(
|
||||||
|
&self,
|
||||||
|
event: &GuildCreate,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
let server = match event {
|
let server = match event {
|
||||||
GuildCreate::Available(guild) => Server::from(guild.clone()),
|
GuildCreate::Available(guild) => Server::from(guild.clone()),
|
||||||
GuildCreate::Unavailable(_) => {
|
GuildCreate::Unavailable(_) => {
|
||||||
@@ -51,8 +56,12 @@ impl OnServerCreateHandler<State> for ServerCreateHandler {
|
|||||||
pub struct ServerUpdateHandler;
|
pub struct ServerUpdateHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnServerUpdateHandler<State> for ServerUpdateHandler {
|
impl OnServerUpdateHandler<State, Extras> for ServerUpdateHandler {
|
||||||
async fn on_server_update(&self, event: &GuildUpdate, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_server_update(
|
||||||
|
&self,
|
||||||
|
event: &GuildUpdate,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
let mut server = match Server::get_by_id(event.0.id.get(), &rt.state.db)
|
let mut server = match Server::get_by_id(event.0.id.get(), &rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
@@ -83,8 +92,12 @@ impl OnServerUpdateHandler<State> for ServerUpdateHandler {
|
|||||||
pub struct ServerDeleteHandler;
|
pub struct ServerDeleteHandler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnServerDeleteHandler<State> for ServerDeleteHandler {
|
impl OnServerDeleteHandler<State, Extras> for ServerDeleteHandler {
|
||||||
async fn on_server_delete(&self, event: &GuildDelete, rt: Arc<Runtime<State>>) -> EventResult {
|
async fn on_server_delete(
|
||||||
|
&self,
|
||||||
|
event: &GuildDelete,
|
||||||
|
rt: Arc<Runtime<State, Extras>>,
|
||||||
|
) -> EventResult {
|
||||||
Server::delete_by_id(event.id.get(), &rt.state.db)
|
Server::delete_by_id(event.id.get(), &rt.state.db)
|
||||||
.await
|
.await
|
||||||
.inspect(|_| log::debug!("Deleted server with ID {} because Payphone left", event.id))
|
.inspect(|_| log::debug!("Deleted server with ID {} because Payphone left", event.id))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use twilight_model::{
|
|||||||
use twilight_util::permission_calculator::PermissionCalculator;
|
use twilight_util::permission_calculator::PermissionCalculator;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
extras::Extras,
|
||||||
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
@@ -14,12 +15,15 @@ use crate::{
|
|||||||
pub struct GuildOnlyPermission;
|
pub struct GuildOnlyPermission;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Permission<State> for GuildOnlyPermission {
|
impl Permission<State, Extras> for GuildOnlyPermission {
|
||||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||||
Ok(ctx.event.guild_id.is_some())
|
Ok(ctx.event.guild_id.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_handle_denied(&self, ctx: &CommandContext<State>) -> Result<(), CommandError> {
|
async fn on_handle_denied(
|
||||||
|
&self,
|
||||||
|
ctx: &CommandContext<State, Extras>,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content("❌ This command can only be used in a server.")
|
.content("❌ This command can only be used in a server.")
|
||||||
.await?;
|
.await?;
|
||||||
@@ -32,8 +36,8 @@ impl Permission<State> for GuildOnlyPermission {
|
|||||||
pub struct ServerModeratorPermission;
|
pub struct ServerModeratorPermission;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Permission<State> for ServerModeratorPermission {
|
impl Permission<State, Extras> for ServerModeratorPermission {
|
||||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||||
if let Some(guild_id) = ctx.event.guild_id {
|
if let Some(guild_id) = ctx.event.guild_id {
|
||||||
let Some(guild) = ctx.rt.cache.guild(guild_id) else {
|
let Some(guild) = ctx.rt.cache.guild(guild_id) else {
|
||||||
log::warn!("Could not get guild for ID {guild_id}!");
|
log::warn!("Could not get guild for ID {guild_id}!");
|
||||||
@@ -76,14 +80,19 @@ impl Permission<State> for ServerModeratorPermission {
|
|||||||
)
|
)
|
||||||
.owner_id(guild.owner_id());
|
.owner_id(guild.owner_id());
|
||||||
|
|
||||||
return Ok(calculator.root().contains(Permissions::MANAGE_CHANNELS | Permissions::MANAGE_MESSAGES));
|
return Ok(calculator
|
||||||
|
.root()
|
||||||
|
.contains(Permissions::MANAGE_CHANNELS | Permissions::MANAGE_MESSAGES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DMs channel, the user has all permissions.
|
// DMs channel, the user has all permissions.
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_handle_denied(&self, ctx: &CommandContext<State>) -> Result<(), CommandError> {
|
async fn on_handle_denied(
|
||||||
|
&self,
|
||||||
|
ctx: &CommandContext<State, Extras>,
|
||||||
|
) -> Result<(), CommandError> {
|
||||||
ctx.send()
|
ctx.send()
|
||||||
.content("❌ This command can only be run by a server moderator.\n-# 🛈 Moderators must have permission to manage messages and to manage channels.")
|
.content("❌ This command can only be run by a server moderator.\n-# 🛈 Moderators must have permission to manage messages and to manage channels.")
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
+38
-5
@@ -101,6 +101,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
error::Error,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::Path,
|
path::Path,
|
||||||
process::Command,
|
process::Command,
|
||||||
@@ -115,17 +116,22 @@ use tokio::net::TcpListener;
|
|||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use twilight_cache_inmemory::ResourceType;
|
use twilight_cache_inmemory::ResourceType;
|
||||||
use twilight_gateway::{Intents, ShardId};
|
use twilight_gateway::{Intents, ShardId};
|
||||||
use twilight_model::gateway::{
|
use twilight_model::{
|
||||||
|
gateway::{
|
||||||
payload::outgoing::update_presence::UpdatePresencePayload,
|
payload::outgoing::update_presence::UpdatePresencePayload,
|
||||||
presence::{Activity, ActivityType, Status},
|
presence::{Activity, ActivityType, Status},
|
||||||
|
},
|
||||||
|
user::User as TwilightUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{Args, Commands},
|
cli::{Args, Commands},
|
||||||
constants::DEFAULT_PREFIX,
|
constants::DEFAULT_PREFIX,
|
||||||
database::models::Server,
|
database::models::{Server, User},
|
||||||
|
extras::Extras,
|
||||||
framework::{
|
framework::{
|
||||||
bot::{Bot, PrefixGetter},
|
bot::{Bot, PrefixGetter},
|
||||||
|
commands::extras::ExtrasGetter,
|
||||||
runtime::Runtime,
|
runtime::Runtime,
|
||||||
},
|
},
|
||||||
groups::{
|
groups::{
|
||||||
@@ -136,6 +142,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
gateway::events::{ReadyHandler, ReconnectHandler},
|
gateway::events::{ReadyHandler, ReconnectHandler},
|
||||||
leveling::{commands::LeaderboardCommand, events::MessageLevelingHandler},
|
leveling::{commands::LeaderboardCommand, events::MessageLevelingHandler},
|
||||||
|
moderation::commands::{IncidentsCommand, ModeratorCommand},
|
||||||
servers::{
|
servers::{
|
||||||
commands::PrefixCommand,
|
commands::PrefixCommand,
|
||||||
events::{ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler},
|
events::{ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler},
|
||||||
@@ -148,6 +155,7 @@ mod cli;
|
|||||||
mod constants;
|
mod constants;
|
||||||
mod database;
|
mod database;
|
||||||
mod discord;
|
mod discord;
|
||||||
|
mod extras;
|
||||||
mod framework;
|
mod framework;
|
||||||
mod groups;
|
mod groups;
|
||||||
mod logging;
|
mod logging;
|
||||||
@@ -176,6 +184,7 @@ pub async fn main() {
|
|||||||
database_password,
|
database_password,
|
||||||
shard,
|
shard,
|
||||||
redis_urls,
|
redis_urls,
|
||||||
|
bot_owner_id,
|
||||||
} => {
|
} => {
|
||||||
start(
|
start(
|
||||||
token,
|
token,
|
||||||
@@ -184,6 +193,7 @@ pub async fn main() {
|
|||||||
database_password,
|
database_password,
|
||||||
shard.into(),
|
shard.into(),
|
||||||
redis_urls,
|
redis_urls,
|
||||||
|
bot_owner_id
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -197,10 +207,10 @@ pub async fn main() {
|
|||||||
struct ServerPrefixOrPayphoneMention;
|
struct ServerPrefixOrPayphoneMention;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PrefixGetter<State> for ServerPrefixOrPayphoneMention {
|
impl PrefixGetter<State, Extras> for ServerPrefixOrPayphoneMention {
|
||||||
async fn get(
|
async fn get(
|
||||||
&self,
|
&self,
|
||||||
rt: &Runtime<State>,
|
rt: &Runtime<State, Extras>,
|
||||||
server_id: Option<u64>,
|
server_id: Option<u64>,
|
||||||
_user_id: Option<u64>,
|
_user_id: Option<u64>,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
@@ -218,6 +228,25 @@ impl PrefixGetter<State> for ServerPrefixOrPayphoneMention {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PayphoneExtrasGetter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ExtrasGetter<State> for PayphoneExtrasGetter {
|
||||||
|
type Extras = Extras;
|
||||||
|
|
||||||
|
async fn get_extra(
|
||||||
|
&self,
|
||||||
|
user: &TwilightUser,
|
||||||
|
rt: &Runtime<State, Self::Extras>,
|
||||||
|
) -> Result<Self::Extras, Box<dyn Error>> {
|
||||||
|
log::trace!("Getting extras!");
|
||||||
|
|
||||||
|
let user = User::get_or_create_and_set_last_seen(user.clone(), &rt.state.db).await?;
|
||||||
|
|
||||||
|
Ok(Extras { user })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts a Payphone shard.
|
/// Starts a Payphone shard.
|
||||||
async fn start(
|
async fn start(
|
||||||
token: String,
|
token: String,
|
||||||
@@ -226,6 +255,7 @@ async fn start(
|
|||||||
database_password: Option<String>,
|
database_password: Option<String>,
|
||||||
shard: ShardId,
|
shard: ShardId,
|
||||||
redis_urls: Vec<String>,
|
redis_urls: Vec<String>,
|
||||||
|
bot_owner_id: u64,
|
||||||
) {
|
) {
|
||||||
log::info!("Welcome to Payphone, Nyeki!");
|
log::info!("Welcome to Payphone, Nyeki!");
|
||||||
log::info!(
|
log::info!(
|
||||||
@@ -246,9 +276,10 @@ async fn start(
|
|||||||
let state = State {
|
let state = State {
|
||||||
db,
|
db,
|
||||||
redis: redis_pool,
|
redis: redis_pool,
|
||||||
|
bot_owner_id
|
||||||
};
|
};
|
||||||
|
|
||||||
let bot = Bot::new(state, ServerPrefixOrPayphoneMention)
|
let bot = Bot::new(state, ServerPrefixOrPayphoneMention, PayphoneExtrasGetter)
|
||||||
.shard(shard)
|
.shard(shard)
|
||||||
.intent(Intents::GUILDS)
|
.intent(Intents::GUILDS)
|
||||||
.intent(Intents::GUILD_MESSAGES)
|
.intent(Intents::GUILD_MESSAGES)
|
||||||
@@ -260,6 +291,8 @@ async fn start(
|
|||||||
.command(FriendRequestCommand)
|
.command(FriendRequestCommand)
|
||||||
.command(PrefixCommand)
|
.command(PrefixCommand)
|
||||||
.command(LeaderboardCommand)
|
.command(LeaderboardCommand)
|
||||||
|
.command(ModeratorCommand)
|
||||||
|
.command(IncidentsCommand)
|
||||||
.component(HelpGoBackButton)
|
.component(HelpGoBackButton)
|
||||||
.cache_resource_type(ResourceType::GUILD)
|
.cache_resource_type(ResourceType::GUILD)
|
||||||
.cache_resource_type(ResourceType::CHANNEL)
|
.cache_resource_type(ResourceType::CHANNEL)
|
||||||
|
|||||||
@@ -24,4 +24,5 @@ use scylla::client::session::Session;
|
|||||||
pub struct State {
|
pub struct State {
|
||||||
pub db: Session,
|
pub db: Session,
|
||||||
pub redis: Pool,
|
pub redis: Pool,
|
||||||
|
pub bot_owner_id: u64,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user