From 21678239b5b8b7e1b21a2f081b74faa2380887f4 Mon Sep 17 00:00:00 2001 From: Rafael Bradley Date: Sat, 7 Mar 2026 19:27:40 -0300 Subject: [PATCH] Checkpoint --- Cargo.lock | 2 +- Cargo.toml | 2 +- rustfmt.toml | 2 + src/cli.rs | 4 +- src/colors.rs | 14 ++- src/database/mod.rs | 7 +- src/database/models.rs | 46 +++---- src/framework/bot.rs | 32 ++--- src/framework/commands/arguments.rs | 9 +- src/framework/commands/context.rs | 15 ++- src/framework/commands/messages.rs | 33 ++--- src/framework/commands/mod.rs | 15 ++- src/framework/commands/permissions.rs | 3 +- src/framework/commands/runner.rs | 10 +- src/framework/events/traits.rs | 7 +- src/framework/interactions/components.rs | 12 +- src/framework/interactions/context.rs | 18 ++- src/framework/interactions/registry.rs | 16 +-- src/framework/interactions/runner.rs | 8 +- src/framework/runtime.rs | 9 +- src/framework/tasks.rs | 3 +- src/groups/about/commands.rs | 85 +++++++++---- src/groups/about/components.rs | 16 ++- src/groups/about/embeds.rs | 20 ++- src/groups/about/tasks.rs | 16 ++- src/groups/calls/commands.rs | 90 +++++++------- src/groups/calls/components.rs | 4 +- src/groups/calls/embeds.rs | 15 ++- src/groups/calls/events.rs | 117 ++++++++++-------- .../calls/lists/explicit-sexual-language.txt | 13 +- src/groups/calls/moderation.rs | 4 +- src/groups/calls/permissions.rs | 61 +++++++-- src/groups/calls/strings.rs | 3 +- src/groups/calls/tasks.rs | 11 +- src/groups/gateway/events.rs | 12 +- src/groups/leveling/commands.rs | 58 +++++---- src/groups/leveling/events.rs | 18 ++- src/groups/moderation/arguments.rs | 41 +++--- src/groups/moderation/commands.rs | 32 ++--- src/groups/moderation/components.rs | 16 +-- src/groups/moderation/embeds.rs | 8 +- src/groups/moderation/mod.rs | 18 ++- src/groups/moderation/permissions.rs | 10 +- src/groups/servers/commands.rs | 24 ++-- src/groups/servers/events.rs | 16 +-- src/groups/servers/permissions.rs | 17 ++- src/logging.rs | 3 +- src/main.rs | 82 ++++++------ src/redis.rs | 6 +- src/utils.rs | 22 ++++ 50 files changed, 599 insertions(+), 506 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index b821baf..da3042a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2013,7 +2013,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "payphone" -version = "4.4.35" +version = "4.4.41" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 819d8c3..fc21b72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "payphone" -version = "4.4.35" +version = "4.4.41" edition = "2024" default-run = "payphone" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..64d94de --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" diff --git a/src/cli.rs b/src/cli.rs index 3bac15b..d4c9e01 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,9 @@ //! starting the server. You can also use the `--bind` argument to specify the address to bind the //! server to, which defaults to `0.0.0.0:3000`. -use std::{fmt, net::SocketAddr, str::FromStr}; +use std::fmt; +use std::net::SocketAddr; +use std::str::FromStr; use twilight_gateway::ShardId; diff --git a/src/colors.rs b/src/colors.rs index ceb176a..c67da94 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,15 +1,17 @@ -use std::{io::Cursor, sync::Arc}; +use std::io::Cursor; +use std::sync::Arc; use color_thief::ColorFormat; use image::{GenericImageView, ImageReader}; use reqwest::header::USER_AGENT; use rgb::Rgb; -use tokio::sync::{Semaphore, mpsc::Receiver}; +use tokio::sync::Semaphore; +use tokio::sync::mpsc::Receiver; -use crate::{ - constants::VERSION, database::models::User, - discord::images::get_user_custom_avatar_url_or_none, state::State, -}; +use crate::constants::VERSION; +use crate::database::models::User; +use crate::discord::images::get_user_custom_avatar_url_or_none; +use crate::state::State; pub struct AvatarColorFetchingRequest { pub hash: String, diff --git a/src/database/mod.rs b/src/database/mod.rs index 5b626a9..1d3cc9f 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,10 +1,9 @@ pub mod models; use include_dir::{Dir, include_dir}; -use scylla::{ - client::{session::Session, session_builder::SessionBuilder}, - errors::ExecutionError, -}; +use scylla::client::session::Session; +use scylla::client::session_builder::SessionBuilder; +use scylla::errors::ExecutionError; use thiserror::Error; use time::OffsetDateTime; diff --git a/src/database/models.rs b/src/database/models.rs index e9cbcf9..c85fee1 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,37 +1,29 @@ use std::collections::HashSet; -use scylla::{ - DeserializeValue, SerializeValue, - client::session::Session, - deserialize::row::DeserializeRow, - errors::{ - DeserializationError, ExecutionError, FirstRowError, IntoRowsResultError, RowsError, - SingleRowError, - }, - response::query_result::QueryRowsResult, - statement::{ - Statement, - batch::{Batch, BatchType}, - }, +use scylla::client::session::Session; +use scylla::deserialize::row::DeserializeRow; +use scylla::errors::{ + DeserializationError, ExecutionError, FirstRowError, IntoRowsResultError, RowsError, + SingleRowError, }; +use scylla::response::query_result::QueryRowsResult; +use scylla::statement::Statement; +use scylla::statement::batch::{Batch, BatchType}; +use scylla::{DeserializeValue, SerializeValue}; use thiserror::Error; use time::{Duration, OffsetDateTime, UtcDateTime, UtcOffset}; use tokio::sync::mpsc::Sender; use twilight_http::Client; -use twilight_model::{ - channel::{Message as TwilightMessage, Webhook}, - guild::Guild as TwilightGuild, - id::Id, - user::User as TwilightUser, -}; +use twilight_model::channel::{Message as TwilightMessage, Webhook}; +use twilight_model::guild::Guild as TwilightGuild; +use twilight_model::id::Id; +use twilight_model::user::User as TwilightUser; use twilight_validate::request::{ValidationError, ValidationErrorType, webhook_username}; -use crate::{ - colors::AvatarColorFetchingRequest, - constants::DEFAULT_PREFIX, - groups::calls::masks::{get_mask_avatar_url, get_mask_display_name, get_random_mask}, - utils::{Bye, get_random_u64, i64_to_u64, u64_to_i64}, -}; +use crate::colors::AvatarColorFetchingRequest; +use crate::constants::DEFAULT_PREFIX; +use crate::groups::calls::masks::{get_mask_avatar_url, get_mask_display_name, get_random_mask}; +use crate::utils::{Bye, get_random_u64, i64_to_u64, u64_to_i64}; #[derive(Debug, Error)] pub enum DatabaseOrDiscordError { @@ -1346,9 +1338,7 @@ impl Message { propagation_message_id = ? LIMIT 1; "#, - ( - u64_to_i64(propagation_message_id), - ), + (u64_to_i64(propagation_message_id),), ) .await? .into_rows_result()?; diff --git a/src/framework/bot.rs b/src/framework/bot.rs index 26cea17..09105b9 100644 --- a/src/framework/bot.rs +++ b/src/framework/bot.rs @@ -1,4 +1,6 @@ -use std::{fmt::Debug, sync::Arc, time::Duration}; +use std::fmt::Debug; +use std::sync::Arc; +use std::time::Duration; use async_trait::async_trait; use thiserror::Error; @@ -8,22 +10,20 @@ use twilight_gateway::{ConfigBuilder, Event, EventTypeFlags, Intents, Shard, Sha use twilight_http::Client; use twilight_model::gateway::payload::outgoing::update_presence::UpdatePresencePayload; -use crate::{ - framework::{ - commands::{Command, extras::ExtrasGetter, runner::CommandRunner}, - events::traits::{ - OnInteractionHandler, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler, - OnReadyHandler, OnReconnectHandler, OnServerCreateHandler, OnServerDeleteHandler, - OnServerUpdateHandler, - }, - interactions::{ - components::Component, registry::ComponentRegistry, runner::InteractionRunner, - }, - runtime::Runtime, - tasks::Task, - }, - utils::Bye, +use crate::framework::commands::Command; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::commands::runner::CommandRunner; +use crate::framework::events::traits::{ + OnInteractionHandler, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler, + OnReadyHandler, OnReconnectHandler, OnServerCreateHandler, OnServerDeleteHandler, + OnServerUpdateHandler, }; +use crate::framework::interactions::components::Component; +use crate::framework::interactions::registry::ComponentRegistry; +use crate::framework::interactions::runner::InteractionRunner; +use crate::framework::runtime::Runtime; +use crate::framework::tasks::Task; +use crate::utils::Bye; /// Errors occurred while attempting to run the bot. #[derive(Debug, Error)] diff --git a/src/framework/commands/arguments.rs b/src/framework/commands/arguments.rs index 25b8787..abaa826 100644 --- a/src/framework/commands/arguments.rs +++ b/src/framework/commands/arguments.rs @@ -1,11 +1,10 @@ -use std::{ - fmt, - ops::{Deref, DerefMut}, -}; +use std::fmt; +use std::ops::{Deref, DerefMut}; use thiserror::Error; -use crate::{database::models::DatabaseError, framework::commands::context::CommandContext}; +use crate::database::models::DatabaseError; +use crate::framework::commands::context::CommandContext; #[derive(Debug, Error)] pub enum ArgumentParsingError { diff --git a/src/framework/commands/context.rs b/src/framework/commands/context.rs index 609ce04..50c97ce 100644 --- a/src/framework/commands/context.rs +++ b/src/framework/commands/context.rs @@ -1,15 +1,14 @@ use std::sync::Arc; use twilight_http::{Error, Response}; -use twilight_model::{ - channel::{Message, message::Component}, - gateway::payload::incoming::MessageCreate, -}; +use twilight_model::channel::Message; +use twilight_model::channel::message::Component; +use twilight_model::gateway::payload::incoming::MessageCreate; -use crate::framework::{ - commands::messages::MessageSender, interactions::components::ActionRow, - messages::MessageBuilder, runtime::Runtime, -}; +use crate::framework::commands::messages::MessageSender; +use crate::framework::interactions::components::ActionRow; +use crate::framework::messages::MessageBuilder; +use crate::framework::runtime::Runtime; pub struct CommandContext<'a, T, E> { pub event: &'a MessageCreate, diff --git a/src/framework/commands/messages.rs b/src/framework/commands/messages.rs index e2e5716..63e7a80 100644 --- a/src/framework/commands/messages.rs +++ b/src/framework/commands/messages.rs @@ -1,27 +1,18 @@ -use std::{ - future::{Future, IntoFuture}, - pin::Pin, - sync::Arc, -}; +use std::future::{Future, IntoFuture}; +use std::pin::Pin; +use std::sync::Arc; -use twilight_http::{Error, Response, response::marker::EmptyBody}; -use twilight_model::{ - channel::{ - Message, - message::{Component as TwilightComponent, Embed}, - }, - http::interaction::{InteractionResponse, InteractionResponseType}, - id::{ - Id, - marker::{ChannelMarker, InteractionMarker}, - }, -}; +use twilight_http::response::marker::EmptyBody; +use twilight_http::{Error, Response}; +use twilight_model::channel::Message; +use twilight_model::channel::message::{Component as TwilightComponent, Embed}; +use twilight_model::http::interaction::{InteractionResponse, InteractionResponseType}; +use twilight_model::id::Id; +use twilight_model::id::marker::{ChannelMarker, InteractionMarker}; use twilight_util::builder::InteractionResponseDataBuilder; -use crate::framework::{ - interactions::components::{ActionRow, Component}, - runtime::Runtime, -}; +use crate::framework::interactions::components::{ActionRow, Component}; +use crate::framework::runtime::Runtime; pub struct MessageSender { channel_id: Id, diff --git a/src/framework/commands/mod.rs b/src/framework/commands/mod.rs index 3311332..55437ef 100644 --- a/src/framework/commands/mod.rs +++ b/src/framework/commands/mod.rs @@ -1,17 +1,16 @@ use std::fmt::Debug; use async_trait::async_trait; -use deadpool_redis::{cluster::PoolError, redis::RedisError}; +use deadpool_redis::cluster::PoolError; +use deadpool_redis::redis::RedisError; use thiserror::Error; use twilight_http::response::DeserializeBodyError; -use crate::{ - database::models::{DatabaseError, DatabaseOrDiscordError}, - framework::{ - commands::{arguments::Argument, context::CommandContext, permissions::Permission}, - runtime::Runtime, - }, -}; +use crate::database::models::{DatabaseError, DatabaseOrDiscordError}; +use crate::framework::commands::arguments::Argument; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::framework::runtime::Runtime; pub mod arguments; pub mod context; diff --git a/src/framework/commands/permissions.rs b/src/framework/commands/permissions.rs index d823328..78d395a 100644 --- a/src/framework/commands/permissions.rs +++ b/src/framework/commands/permissions.rs @@ -2,7 +2,8 @@ use std::fmt::Debug; use async_trait::async_trait; -use crate::framework::commands::{CommandError, context::CommandContext}; +use crate::framework::commands::CommandError; +use crate::framework::commands::context::CommandContext; #[async_trait] pub trait Permission: Debug { diff --git a/src/framework/commands/runner.rs b/src/framework/commands/runner.rs index e8eb412..5fcf427 100644 --- a/src/framework/commands/runner.rs +++ b/src/framework/commands/runner.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use async_trait::async_trait; use twilight_model::gateway::payload::incoming::MessageCreate; -use crate::framework::{ - commands::{context::CommandContext, extras::ExtrasGetter, get_command_parts}, - events::traits::{EventResult, OnMessageHandler}, - runtime::Runtime, -}; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::commands::get_command_parts; +use crate::framework::events::traits::{EventResult, OnMessageHandler}; +use crate::framework::runtime::Runtime; #[derive(Debug)] pub struct CommandRunner; diff --git a/src/framework/events/traits.rs b/src/framework/events/traits.rs index b1767ab..ecc3551 100644 --- a/src/framework/events/traits.rs +++ b/src/framework/events/traits.rs @@ -1,4 +1,6 @@ -use std::{error::Error, fmt::Debug, sync::Arc}; +use std::error::Error; +use std::fmt::Debug; +use std::sync::Arc; use async_trait::async_trait; use twilight_model::gateway::payload::incoming::{ @@ -6,7 +8,8 @@ use twilight_model::gateway::payload::incoming::{ MessageUpdate, Ready, }; -use crate::framework::{commands::extras::ExtrasGetter, runtime::Runtime}; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::runtime::Runtime; pub type EventResult = Result<(), Box>; diff --git a/src/framework/interactions/components.rs b/src/framework/interactions/components.rs index 2264e76..023d10c 100644 --- a/src/framework/interactions/components.rs +++ b/src/framework/interactions/components.rs @@ -1,12 +1,14 @@ -use std::{fmt::Debug, sync::Arc}; +use std::fmt::Debug; +use std::sync::Arc; use async_trait::async_trait; -use twilight_model::channel::message::{ - Component as TwilightComponent, - component::{ActionRow as TwilightActionRow, Button as TwilightButton, ButtonStyle}, +use twilight_model::channel::message::Component as TwilightComponent; +use twilight_model::channel::message::component::{ + ActionRow as TwilightActionRow, Button as TwilightButton, ButtonStyle, }; -use crate::framework::interactions::{InteractionError, context::InteractionContext}; +use crate::framework::interactions::InteractionError; +use crate::framework::interactions::context::InteractionContext; type ComponentWithID = (Option, Arc>); diff --git a/src/framework/interactions/context.rs b/src/framework/interactions/context.rs index 871a86a..9115557 100644 --- a/src/framework/interactions/context.rs +++ b/src/framework/interactions/context.rs @@ -1,17 +1,15 @@ use std::sync::Arc; -use twilight_http::{Error, Response, response::marker::EmptyBody}; -use twilight_model::{ - gateway::payload::incoming::InteractionCreate, - http::interaction::{InteractionResponse, InteractionResponseType}, -}; +use twilight_http::response::marker::EmptyBody; +use twilight_http::{Error, Response}; +use twilight_model::gateway::payload::incoming::InteractionCreate; +use twilight_model::http::interaction::{InteractionResponse, InteractionResponseType}; use twilight_util::builder::InteractionResponseDataBuilder; -use crate::framework::{ - interactions::{InteractionError, components::ActionRow}, - messages::MessageEditBuilder, - runtime::Runtime, -}; +use crate::framework::interactions::InteractionError; +use crate::framework::interactions::components::ActionRow; +use crate::framework::messages::MessageEditBuilder; +use crate::framework::runtime::Runtime; pub struct InteractionContext { pub event: InteractionCreate, diff --git a/src/framework/interactions/registry.rs b/src/framework/interactions/registry.rs index 39eddb4..4d4c014 100644 --- a/src/framework/interactions/registry.rs +++ b/src/framework/interactions/registry.rs @@ -1,18 +1,14 @@ use std::sync::Arc; use scc::HashMap; - -use twilight_model::{ - application::interaction::InteractionData, gateway::payload::incoming::InteractionCreate, -}; - -use crate::framework::{ - commands::extras::ExtrasGetter, - interactions::{InteractionError, context::InteractionContext}, - runtime::Runtime, -}; +use twilight_model::application::interaction::InteractionData; +use twilight_model::gateway::payload::incoming::InteractionCreate; use super::components::Component; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::interactions::InteractionError; +use crate::framework::interactions::context::InteractionContext; +use crate::framework::runtime::Runtime; #[derive(Default)] pub struct ComponentRegistry { diff --git a/src/framework/interactions/runner.rs b/src/framework/interactions/runner.rs index aa0df7f..7ae166c 100644 --- a/src/framework/interactions/runner.rs +++ b/src/framework/interactions/runner.rs @@ -3,11 +3,9 @@ use std::sync::Arc; use async_trait::async_trait; use twilight_model::gateway::payload::incoming::InteractionCreate; -use crate::framework::{ - commands::extras::ExtrasGetter, - events::traits::{EventResult, OnInteractionHandler}, - runtime::Runtime, -}; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::events::traits::{EventResult, OnInteractionHandler}; +use crate::framework::runtime::Runtime; #[derive(Debug)] pub struct InteractionRunner; diff --git a/src/framework/runtime.rs b/src/framework/runtime.rs index 1ab1514..332c12c 100644 --- a/src/framework/runtime.rs +++ b/src/framework/runtime.rs @@ -22,11 +22,12 @@ use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; use twilight_cache_inmemory::InMemoryCache; use twilight_http::Client; -use twilight_model::{gateway::payload::outgoing::UpdatePresence, oauth::Application}; +use twilight_model::gateway::payload::outgoing::UpdatePresence; +use twilight_model::oauth::Application; -use crate::framework::{ - bot::PrefixGetter, commands::Command, interactions::registry::ComponentRegistry, -}; +use crate::framework::bot::PrefixGetter; +use crate::framework::commands::Command; +use crate::framework::interactions::registry::ComponentRegistry; /// The application's runtime tools. pub struct Runtime { diff --git a/src/framework/tasks.rs b/src/framework/tasks.rs index 3794bfe..49b02f4 100644 --- a/src/framework/tasks.rs +++ b/src/framework/tasks.rs @@ -1,4 +1,5 @@ -use std::{fmt::Debug, time::Duration}; +use std::fmt::Debug; +use std::time::Duration; use async_trait::async_trait; diff --git a/src/groups/about/commands.rs b/src/groups/about/commands.rs index 9c118b4..fcb3aaa 100644 --- a/src/groups/about/commands.rs +++ b/src/groups/about/commands.rs @@ -1,29 +1,18 @@ use async_trait::async_trait; -use crate::{ - extras::Extras, - framework::{ - commands::{ - Command, CommandError, - arguments::{Arg, Argument, NeedyString, ParseArguments}, - context::CommandContext, - }, - runtime::Runtime, - }, - groups::{ - about::{ - components::{ - HelpDonateLinkButton, HelpGoBackButton, HelpSupportLinkButton, - HelpWebsiteLinkButton, - }, - embeds::{ - get_all_command_help_embed, get_command_help_embed, get_command_not_found_embed, - }, - }, - calls::components::CodeOfConductButton, - }, - state::State, +use crate::extras::Extras; +use crate::framework::commands::arguments::{Arg, Argument, NeedyString, ParseArguments}; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::{Command, CommandError}; +use crate::framework::runtime::Runtime; +use crate::groups::about::components::{ + HelpDonateLinkButton, HelpGoBackButton, HelpSupportLinkButton, HelpWebsiteLinkButton, }; +use crate::groups::about::embeds::{ + get_all_command_help_embed, get_command_help_embed, get_command_not_found_embed, +}; +use crate::groups::calls::components::CodeOfConductButton; +use crate::state::State; #[derive(Debug)] pub struct HelpCommand; @@ -83,12 +72,20 @@ impl Command for HelpCommand { } #[derive(Debug)] -pub struct TetoCommand; +pub struct CodeOfConductCommand; #[async_trait] -impl Command for TetoCommand { +impl Command for CodeOfConductCommand { fn name(&self) -> &'static str { - "teto" + "codeofconduct" + } + + fn aliases(&self) -> &'static [&'static str] { + &["coc"] + } + + fn description(&self) -> &'static str { + "Shows a link to Payphone's Code of Conduct." } fn show_in_help(&self, _rt: &Runtime, _extras: &Extras) -> bool { @@ -100,7 +97,41 @@ impl Command for TetoCommand { ctx: CommandContext<'_, State, Extras>, _args: &str, ) -> Result<(), CommandError> { - ctx.send().content("GLORY TO TETOSLAVIA").await?; + ctx.send() + .content("https://payphone.lat/code-of-conduct") + .await?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct DonateCommand; + +#[async_trait] +impl Command for DonateCommand { + fn name(&self) -> &'static str { + "donate" + } + + fn aliases(&self) -> &'static [&'static str] { + &["d"] + } + + fn description(&self) -> &'static str { + "Shows a link to Payphone's Ko-Fi page." + } + + fn show_in_help(&self, _rt: &Runtime, _extras: &Extras) -> bool { + false + } + + async fn execute( + &self, + ctx: CommandContext<'_, State, Extras>, + _args: &str, + ) -> Result<(), CommandError> { + ctx.send().content("https://ko-fi.com/payphonedev").await?; Ok(()) } diff --git a/src/groups/about/components.rs b/src/groups/about/components.rs index 7d79801..6401b08 100644 --- a/src/groups/about/components.rs +++ b/src/groups/about/components.rs @@ -1,14 +1,12 @@ use async_trait::async_trait; -use crate::{ - extras::Extras, - framework::{ - interactions::{InteractionError, components::Button, context::InteractionContext}, - messages::MessageEditBuilder, - }, - groups::about::embeds::get_all_command_help_embed, - state::State, -}; +use crate::extras::Extras; +use crate::framework::interactions::InteractionError; +use crate::framework::interactions::components::Button; +use crate::framework::interactions::context::InteractionContext; +use crate::framework::messages::MessageEditBuilder; +use crate::groups::about::embeds::get_all_command_help_embed; +use crate::state::State; #[derive(Debug)] pub struct HelpGoBackButton; diff --git a/src/groups/about/embeds.rs b/src/groups/about/embeds.rs index 49163dd..518c9bf 100644 --- a/src/groups/about/embeds.rs +++ b/src/groups/about/embeds.rs @@ -5,17 +5,15 @@ use twilight_util::builder::embed::{ EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder, ImageSource, }; -use crate::{ - constants::{COLOR_RED, COLOR_WHITE, VERSION}, - discord::{images, sanitizing::SanitizeDiscordMarkdown}, - extras::Extras, - framework::{ - commands::{Command, context::CommandContext}, - runtime::Runtime, - }, - state::State, - utils::ToTitleCase, -}; +use crate::constants::{COLOR_RED, COLOR_WHITE, VERSION}; +use crate::discord::images; +use crate::discord::sanitizing::SanitizeDiscordMarkdown; +use crate::extras::Extras; +use crate::framework::commands::Command; +use crate::framework::commands::context::CommandContext; +use crate::framework::runtime::Runtime; +use crate::state::State; +use crate::utils::ToTitleCase; pub(super) fn get_command_help_embed( command: &Arc>, diff --git a/src/groups/about/tasks.rs b/src/groups/about/tasks.rs index 4c0dbc5..1be12d9 100644 --- a/src/groups/about/tasks.rs +++ b/src/groups/about/tasks.rs @@ -1,16 +1,14 @@ use async_trait::async_trait; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use twilight_model::gateway::{ - payload::outgoing::{UpdatePresence, update_presence::UpdatePresencePayload}, - presence::{Activity, ActivityType, Status}, -}; +use twilight_model::gateway::payload::outgoing::UpdatePresence; +use twilight_model::gateway::payload::outgoing::update_presence::UpdatePresencePayload; +use twilight_model::gateway::presence::{Activity, ActivityType, Status}; -use crate::{ - extras::Extras, - framework::{runtime::Runtime, tasks::Task}, - state::State, -}; +use crate::extras::Extras; +use crate::framework::runtime::Runtime; +use crate::framework::tasks::Task; +use crate::state::State; #[derive(Debug)] pub struct UpdatePresenceTask; diff --git a/src/groups/calls/commands.rs b/src/groups/calls/commands.rs index a6a18fb..6f5f635 100644 --- a/src/groups/calls/commands.rs +++ b/src/groups/calls/commands.rs @@ -4,28 +4,24 @@ use scc::HashSet; use time::UtcDateTime; use twilight_model::id::Id; -use crate::{ - database::models::{Call, Channel, Participant}, - discord::sanitizing::SanitizeDiscordMarkdown, - extras::Extras, - framework::commands::{ - Command, CommandError, - arguments::{Arg, Argument, ParseArguments}, - context::CommandContext, - permissions::Permission, - }, - groups::calls::{ - masks::{get_mask_display_name, get_mask_emoji}, - permissions::{NonNewAccountPermission, NotBannedPermission}, - strings::{ - get_string_call_answered, get_string_call_hangup_other_party, - get_string_call_hangup_this_party, get_string_call_no_answer, - get_string_call_searching, get_string_tips, - }, - }, - state::State, - utils::Bye, +use crate::database::models::{Call, Channel, Participant}; +use crate::discord::sanitizing::SanitizeDiscordMarkdown; +use crate::extras::Extras; +use crate::framework::commands::arguments::{Arg, Argument, ParseArguments}; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::framework::commands::{Command, CommandError}; +use crate::groups::calls::masks::{get_mask_display_name, get_mask_emoji}; +use crate::groups::calls::permissions::{ + IsCallParticipantPermission, NonNewAccountPermission, NotBannedPermission, }; +use crate::groups::calls::strings::{ + get_string_call_answered, get_string_call_hangup_other_party, + get_string_call_hangup_this_party, get_string_call_no_answer, get_string_call_searching, + get_string_tips, +}; +use crate::state::State; +use crate::utils::Bye; #[derive(Default, Debug)] pub struct CallCommand { @@ -172,8 +168,9 @@ impl Command for SkipCommand { fn permissions(&self) -> Vec + Send + Sync + 'static>> { vec![ - Box::new(NotBannedPermission), Box::new(NonNewAccountPermission), + Box::new(NotBannedPermission), + Box::new(IsCallParticipantPermission), ] } @@ -318,8 +315,9 @@ impl Command for HangupCommand { fn permissions(&self) -> Vec + Send + Sync + 'static>> { vec![ - Box::new(NotBannedPermission), Box::new(NonNewAccountPermission), + Box::new(NotBannedPermission), + Box::new(IsCallParticipantPermission), ] } @@ -442,8 +440,9 @@ impl Command for FriendRequestCommand { fn permissions(&self) -> Vec + Send + Sync + 'static>> { vec![ - Box::new(NotBannedPermission), Box::new(NonNewAccountPermission), + Box::new(NotBannedPermission), + Box::new(IsCallParticipantPermission), ] } @@ -598,29 +597,28 @@ impl Command for MaskCommand { mask_status, ctx.prefix, ctx.prefix ); - if let Some(channel) = channel { - if let Some(call_id) = channel.call_id { - if let Some(mut participant) = Participant::get_for_user_and_channel_in_call( - ctx.extras.user.id, - ctx.event.channel_id.get(), - call_id, - &ctx.rt.state.db, - ) - .await? - { - participant.is_masked = ctx.extras.user.is_masked; - participant.upsert(&ctx.rt.state.db).await?; + if let Some(channel) = channel + && let Some(call_id) = channel.call_id + { + if let Some(mut participant) = Participant::get_for_user_and_channel_in_call( + ctx.extras.user.id, + ctx.event.channel_id.get(), + call_id, + &ctx.rt.state.db, + ) + .await? + { + participant.is_masked = ctx.extras.user.is_masked; + participant.upsert(&ctx.rt.state.db).await?; - if ctx.extras.user.is_masked { - mask_message = format!( - "🎭 Your mask {mask_verb} set to `on`. You're currently being displayed as **{} {}**. You can toggle your mask on or off using `{}mask on` and `{}mask off`.", - get_mask_emoji(&participant.user_mask) - .expect("User's mask did not exist"), - get_mask_display_name(&participant.user_mask), - ctx.prefix, - ctx.prefix - ); - } + if ctx.extras.user.is_masked { + mask_message = format!( + "🎭 Your mask {mask_verb} set to `on`. You're currently being displayed as **{} {}**. You can toggle your mask on or off using `{}mask on` and `{}mask off`.", + get_mask_emoji(&participant.user_mask).expect("User's mask did not exist"), + get_mask_display_name(&participant.user_mask), + ctx.prefix, + ctx.prefix + ); } } } diff --git a/src/groups/calls/components.rs b/src/groups/calls/components.rs index 7326be8..20f2567 100644 --- a/src/groups/calls/components.rs +++ b/src/groups/calls/components.rs @@ -1,4 +1,6 @@ -use crate::{extras::Extras, framework::interactions::components::Button, state::State}; +use crate::extras::Extras; +use crate::framework::interactions::components::Button; +use crate::state::State; #[derive(Debug)] pub struct CodeOfConductButton; diff --git a/src/groups/calls/embeds.rs b/src/groups/calls/embeds.rs index b709fc4..9861791 100644 --- a/src/groups/calls/embeds.rs +++ b/src/groups/calls/embeds.rs @@ -1,17 +1,16 @@ use std::sync::Arc; -use twilight_model::{channel::message::Embed, oauth::Application}; +use twilight_model::channel::message::Embed; +use twilight_model::oauth::Application; use twilight_util::builder::embed::{ EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder, ImageSource, }; -use crate::{ - constants::{COLOR_RED, COLOR_YELLOW}, - database::models::Message, - discord::images, - groups::calls::moderation::Rule, - utils::ellipse, -}; +use crate::constants::{COLOR_RED, COLOR_YELLOW}; +use crate::database::models::Message; +use crate::discord::images; +use crate::groups::calls::moderation::Rule; +use crate::utils::ellipse; pub fn first_warning_embed( app: &Application, diff --git a/src/groups/calls/events.rs b/src/groups/calls/events.rs index 2bfc9f2..4f8d82c 100644 --- a/src/groups/calls/events.rs +++ b/src/groups/calls/events.rs @@ -1,41 +1,36 @@ -use std::{error::Error, sync::Arc}; +use std::error::Error; +use std::sync::Arc; use async_trait::async_trait; use deadpool_redis::redis::AsyncTypedCommands; use itertools::Itertools; use time::{Duration, UtcDateTime}; -use twilight_http::{api_error::ApiError, error::ErrorType}; -use twilight_model::{ - gateway::payload::incoming::{MessageCreate, MessageDelete, MessageUpdate}, - id::Id, -}; -use twilight_util::{ - builder::embed::{EmbedAuthorBuilder, EmbedBuilder, EmbedFooterBuilder, ImageSource}, - snowflake::Snowflake, +use twilight_http::api_error::ApiError; +use twilight_http::error::ErrorType; +use twilight_model::gateway::payload::incoming::{MessageCreate, MessageDelete, MessageUpdate}; +use twilight_model::id::Id; +use twilight_util::builder::embed::{ + EmbedAuthorBuilder, EmbedBuilder, EmbedFooterBuilder, ImageSource, }; +use twilight_util::snowflake::Snowflake; -use crate::{ - constants::{COLOR_WHITE, COLOR_YELLOW}, - database::models::{Call, Channel, Message, Participant, User, UserIncident}, - discord::{images, sanitizing::SanitizeDiscordMarkdown}, - extras::Extras, - framework::{ - commands::extras::ExtrasGetter, - events::traits::{ - EventResult, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler, - }, - interactions::components::ActionRow, - runtime::Runtime, - }, - groups::calls::{ - components::{CodeOfConductButton, NeedHelpButton}, - embeds, - masks::{get_mask_avatar_url, get_mask_display_name}, - moderation::{Action, Rule, escape_message, moderate_message}, - }, - state::State, - utils::{ellipse, i64_to_u64}, +use crate::constants::{COLOR_WHITE, COLOR_YELLOW}; +use crate::database::models::{Call, Channel, Message, Participant, User, UserIncident}; +use crate::discord::images; +use crate::discord::sanitizing::SanitizeDiscordMarkdown; +use crate::extras::Extras; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::events::traits::{ + EventResult, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler, }; +use crate::framework::interactions::components::ActionRow; +use crate::framework::runtime::Runtime; +use crate::groups::calls::components::{CodeOfConductButton, NeedHelpButton}; +use crate::groups::calls::embeds; +use crate::groups::calls::masks::{get_mask_avatar_url, get_mask_display_name}; +use crate::groups::calls::moderation::{Action, Rule, escape_message, moderate_message}; +use crate::state::State; +use crate::utils::{ellipse, i64_to_u64}; async fn penalize_user( user: User, @@ -349,15 +344,35 @@ impl OnMessageHandler for CallMessageHandler { ) }; + // Replied-to links are always + // .../{other_party_server_id}/{other_party_channel_id}/{other_party_message_id} + // + // When the message being replied to was sent by the other party, the + // `other_party_message_id` is the original message ID, given it was sent from + // their channel so the original is in their channel. + // + // When the message being replied to was sent by this party, the + // `other_party_message_id` is the propagation message ID given the other party + // will have seen the propagated message instead. let message_link = if i64_to_u64(propagation_channel_id) == channel.id { + // if the message replied-to was sent by the other party + // + // replied_to.message_id -> other party's channel id + // replied_to.propagation_channel_id -> this channel's ID + // replied_to.propagation_message_id -> this channel's message ID format!( "https://discord.com/channels/{}/{}/{}", opposite_channel.server_id, opposite_channel.id, replying_to.message_id ) } else { + // if the message replied-to was sent by this party + // + // replied_to.message_id -> this channel's message ID + // replied_to.propagation_channel_id -> other party's channel id + // replied_to.propagation_message_id -> other party's message ID format!( "https://discord.com/channels/{}/{}/{}", - channel.server_id, propagation_channel_id, propagation_message_id + opposite_channel.server_id, propagation_channel_id, propagation_message_id ) }; @@ -377,7 +392,10 @@ impl OnMessageHandler for CallMessageHandler { .and_then(|i| i.color.map(|c| c as u32)) .unwrap_or(COLOR_WHITE) }) - .description(ellipse(moderate_message(&replying_to.data.content, false).0, 100)) + .description(ellipse( + moderate_message(&replying_to.data.content, false).0, + 100, + )) .build(), ); } @@ -440,12 +458,13 @@ impl OnMessageHandler for CallMessageHandler { error: ApiError::General(error), .. } = e.kind() - && error.code == 10015 { - log::debug!("Webhook not found, updating..."); - opposite_channel - .update_webhook(&rt.state.db, &rt.http) - .await?; - } + && error.code == 10015 + { + log::debug!("Webhook not found, updating..."); + opposite_channel + .update_webhook(&rt.state.db, &rt.http) + .await?; + } return Ok(()); } } @@ -626,19 +645,19 @@ impl OnMessageDeleteHandler for CallMessageDeleteHandler { && let Some((webhook_id, webhook_token)) = opposite_channel .webhook_id .zip(opposite_channel.webhook_token) - { - rt.http - .delete_webhook_message( - Id::new(webhook_id), - &webhook_token, - Id::new(i64_to_u64(propagation_message_id)), - ) - .await?; + { + rt.http + .delete_webhook_message( + Id::new(webhook_id), + &webhook_token, + Id::new(i64_to_u64(propagation_message_id)), + ) + .await?; - message.data.is_deleted = true; + message.data.is_deleted = true; - message.insert(&rt.state.db).await?; - } + message.insert(&rt.state.db).await?; + } Ok(()) } diff --git a/src/groups/calls/lists/explicit-sexual-language.txt b/src/groups/calls/lists/explicit-sexual-language.txt index cf8fd45..a7bf6cf 100644 --- a/src/groups/calls/lists/explicit-sexual-language.txt +++ b/src/groups/calls/lists/explicit-sexual-language.txt @@ -79,6 +79,9 @@ goonbud goonbuds goonete goonetes +gooner +goonr +goner boty hole suck my pp ass cracker @@ -95,6 +98,10 @@ masturbating masterbating masterbaiting masturbatigng +masterbaiter +mastrbaiter +mastrbait +mastrbate horny hrny whore / who re @@ -116,6 +123,8 @@ eat my bum eat mi bum ima touch you ima touch u +i ma touch you +i ma touch u il touch you il touch u im going to touch you @@ -125,4 +134,6 @@ im gonna touch u nude nudes nde -ndes \ No newline at end of file +ndes +nood +noods \ No newline at end of file diff --git a/src/groups/calls/moderation.rs b/src/groups/calls/moderation.rs index 01054d2..72c2481 100644 --- a/src/groups/calls/moderation.rs +++ b/src/groups/calls/moderation.rs @@ -83,7 +83,9 @@ //! //! All of which would be detected. -use std::{collections::HashMap, fmt::Debug, sync::Arc}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; use itertools::Itertools; use linkify::LinkFinder; diff --git a/src/groups/calls/permissions.rs b/src/groups/calls/permissions.rs index a52908c..c3bb9c8 100644 --- a/src/groups/calls/permissions.rs +++ b/src/groups/calls/permissions.rs @@ -2,13 +2,13 @@ use async_trait::async_trait; use time::{Duration, UtcDateTime}; use twilight_util::snowflake::Snowflake; -use crate::{ - database::models::UserIncident, - extras::Extras, - framework::commands::{CommandError, context::CommandContext, permissions::Permission}, - groups::calls::components::{CodeOfConductButton, NeedHelpButton}, - state::State, -}; +use crate::database::models::{Call, Channel, Participant, UserIncident}; +use crate::extras::Extras; +use crate::framework::commands::CommandError; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::groups::calls::components::{CodeOfConductButton, NeedHelpButton}; +use crate::state::State; #[derive(Debug)] pub struct NotBannedPermission; @@ -72,3 +72,50 @@ impl Permission for NonNewAccountPermission { Ok(()) } } + +/// Requires users to have sent a message in the call to be able to use the command. +/// +/// This is to prevent abuse of commands by lurkers in the call, as well as to prevent +/// non-participants from skipping or ending calls they're not in. +#[derive(Debug)] +pub struct IsCallParticipantPermission; + +#[async_trait] +impl Permission for IsCallParticipantPermission { + async fn check(&self, ctx: &CommandContext) -> Result { + let channel = match Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db).await? + { + Some(channel) => channel, + None => return Ok(true), // The channel isn't in the database, so it can't be in call. + }; + + let call_id = match channel.call_id { + Some(call_id) => call_id, + None => return Ok(true), // The channel isn't in a call, so the user can use commands in it. + }; + + let participants = Participant::get_for_call(call_id, &ctx.rt.state.db).await?; + + for participant in participants { + if participant.user_id == ctx.event.author.id.get() { + return Ok(true); + } + } + + return Ok(false); + } + + async fn on_handle_denied( + &self, + ctx: &CommandContext, + ) -> Result<(), CommandError> { + ctx.send() + .content("❌ You haven't sent any messages on this call. You cannot run this command.") + .await + .inspect_err(|e| { + log::error!("Failed to send message: {e}"); + })?; + + Ok(()) + } +} diff --git a/src/groups/calls/strings.rs b/src/groups/calls/strings.rs index b3c4196..faea388 100644 --- a/src/groups/calls/strings.rs +++ b/src/groups/calls/strings.rs @@ -1,5 +1,6 @@ use once_cell::sync::Lazy; -use rand::{SeedableRng, seq::IndexedRandom}; +use rand::SeedableRng; +use rand::seq::IndexedRandom; use rand_chacha::ChaCha20Rng; static STRINGS_TIPS: Lazy> = Lazy::new(|| { diff --git a/src/groups/calls/tasks.rs b/src/groups/calls/tasks.rs index 053300b..cdc7a90 100644 --- a/src/groups/calls/tasks.rs +++ b/src/groups/calls/tasks.rs @@ -3,12 +3,11 @@ use deadpool_redis::redis::AsyncTypedCommands; use time::{Duration, UtcDateTime}; use twilight_model::id::Id; -use crate::{ - database::models::Call, - extras::Extras, - framework::{runtime::Runtime, tasks::Task}, - state::State, -}; +use crate::database::models::Call; +use crate::extras::Extras; +use crate::framework::runtime::Runtime; +use crate::framework::tasks::Task; +use crate::state::State; #[derive(Debug)] pub struct EndInactiveCallsTask { diff --git a/src/groups/gateway/events.rs b/src/groups/gateway/events.rs index d59486f..4efdb37 100644 --- a/src/groups/gateway/events.rs +++ b/src/groups/gateway/events.rs @@ -5,14 +5,10 @@ use std::sync::Arc; use async_trait::async_trait; use twilight_model::gateway::payload::incoming::Ready; -use crate::{ - extras::Extras, - framework::{ - events::traits::{EventResult, OnReadyHandler, OnReconnectHandler}, - runtime::Runtime, - }, - state::State, -}; +use crate::extras::Extras; +use crate::framework::events::traits::{EventResult, OnReadyHandler, OnReconnectHandler}; +use crate::framework::runtime::Runtime; +use crate::state::State; /// Logs a ready message when the bot is ready. #[derive(Debug)] diff --git a/src/groups/leveling/commands.rs b/src/groups/leveling/commands.rs index eb1ec35..853c52e 100644 --- a/src/groups/leveling/commands.rs +++ b/src/groups/leveling/commands.rs @@ -5,15 +5,16 @@ use twilight_util::builder::embed::{ EmbedAuthorBuilder, EmbedBuilder, EmbedFooterBuilder, ImageSource, }; -use crate::{ - constants::COLOR_WHITE, - database::models::User, - discord::{images, sanitizing::SanitizeDiscordMarkdown}, - extras::Extras, - framework::commands::{Command, CommandError, context::CommandContext}, - groups::leveling::constants::KEY_LEADERBOARD_MONTHLY, - state::State, -}; +use crate::constants::COLOR_WHITE; +use crate::database::models::User; +use crate::discord::images; +use crate::discord::sanitizing::SanitizeDiscordMarkdown; +use crate::extras::Extras; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::{Command, CommandError}; +use crate::groups::leveling::constants::KEY_LEADERBOARD_MONTHLY; +use crate::state::State; +use crate::utils::format_thousands; #[derive(Debug)] pub struct LeaderboardCommand; @@ -48,6 +49,12 @@ impl Command for LeaderboardCommand { let current_user_rank: Option = redis .zrevrank(KEY_LEADERBOARD_MONTHLY, ctx.event.author.id.get()) .await?; + let current_user_xp: Option = redis + .zscore(KEY_LEADERBOARD_MONTHLY, ctx.event.author.id.get()) + .await?; + let current_user_xp = current_user_xp.map(|xp| xp as u64); + + let current_user_rank_and_xp = current_user_rank.zip(current_user_xp); let mut users = Vec::new(); @@ -57,15 +64,15 @@ impl Command for LeaderboardCommand { continue; }; - if let Some(is_banned_from_leaderboard) = user.is_banned_from_leaderboard { - if is_banned_from_leaderboard { - log::trace!( - "Skipping user {} (ID: {}) because they are banned from the leaderboard.", - user.name, - user.id - ); - continue; - } + if let Some(is_banned_from_leaderboard) = user.is_banned_from_leaderboard + && is_banned_from_leaderboard + { + log::trace!( + "Skipping user {} (ID: {}) because they are banned from the leaderboard.", + user.name, + user.id + ); + continue; } users.push((user, score as u32)); @@ -76,15 +83,24 @@ impl Command for LeaderboardCommand { .iter() .enumerate() .map(|(i, (user, xp))| { - format!("#{}: **{}** - {} XP", i + 1, user.name.sanitize(true), xp) + format!( + "#{}: **{}** - {} XP", + i + 1, + user.name.sanitize(true), + format_thousands(*xp as u64) + ) }) .join("\n") } else { "*Nobody earned XP this month yet.*".to_string() }; - let footer = if let Some(rank) = current_user_rank { - format!("Your current rank: #{}", rank + 1) + let footer = if let Some((rank, xp)) = current_user_rank_and_xp { + format!( + "Your current rank: #{} - {} XP", + rank + 1, + format_thousands(xp) + ) } else { "You haven't earned XP this month yet.".to_string() }; diff --git a/src/groups/leveling/events.rs b/src/groups/leveling/events.rs index bf1b617..9784a3d 100644 --- a/src/groups/leveling/events.rs +++ b/src/groups/leveling/events.rs @@ -7,17 +7,13 @@ use rand_chacha::ChaCha20Rng; use time::{Date, Month, OffsetDateTime, Time}; use twilight_model::gateway::payload::incoming::MessageCreate; -use crate::{ - database::models::Channel, - extras::Extras, - framework::{ - commands::extras::ExtrasGetter, - events::traits::{EventResult, OnMessageHandler}, - runtime::Runtime, - }, - groups::leveling::constants::KEY_LEADERBOARD_MONTHLY, - state::State, -}; +use crate::database::models::Channel; +use crate::extras::Extras; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::events::traits::{EventResult, OnMessageHandler}; +use crate::framework::runtime::Runtime; +use crate::groups::leveling::constants::KEY_LEADERBOARD_MONTHLY; +use crate::state::State; fn seconds_until_end_of_month() -> i64 { let now = OffsetDateTime::now_utc(); diff --git a/src/groups/moderation/arguments.rs b/src/groups/moderation/arguments.rs index 9c3f01b..e2e23f1 100644 --- a/src/groups/moderation/arguments.rs +++ b/src/groups/moderation/arguments.rs @@ -1,20 +1,14 @@ -use std::{ - ops::{Deref, DerefMut}, - time::Duration, -}; +use std::ops::{Deref, DerefMut}; +use std::time::Duration; use scylla::client::session::Session; -use crate::{ - database::models::{Message, User}, - extras::Extras, - framework::commands::{ - arguments::{ArgumentParsingError, FromArguments, NeedyString}, - context::CommandContext, - }, - state::State, - utils::split_at_nth_from_end, -}; +use crate::database::models::{Message, User}; +use crate::extras::Extras; +use crate::framework::commands::arguments::{ArgumentParsingError, FromArguments, NeedyString}; +use crate::framework::commands::context::CommandContext; +use crate::state::State; +use crate::utils::split_at_nth_from_end; /// Fetch a user by their ID from the database. /// @@ -110,7 +104,24 @@ impl FromArguments for UserArgument { .parse() .map_err(|_| ArgumentParsingError::InvalidArgument)?; - user = get_user_by_id(user_id, &ctx.rt.state.db).await?; + match get_user_by_id(user_id, &ctx.rt.state.db).await { + Ok(result) => user = result, + Err(error) => { + if matches!(error, ArgumentParsingError::InvalidArgument) { + let message = + Message::get_by_propagation_message_id(user_id, &ctx.rt.state.db) + .await?; + + if let Some(message) = message { + user = get_user_by_id(message.author_id, &ctx.rt.state.db).await?; + } else { + return Err(ArgumentParsingError::InvalidArgument); + } + } else { + return Err(error); + } + } + } } else { // The argument is (probably) a username. diff --git a/src/groups/moderation/commands.rs b/src/groups/moderation/commands.rs index ff59d73..12ffc09 100644 --- a/src/groups/moderation/commands.rs +++ b/src/groups/moderation/commands.rs @@ -1,26 +1,20 @@ use async_trait::async_trait; use time::UtcDateTime; -use crate::{ - database::models::{UserIncident, UserIncidentType}, - extras::Extras, - framework::{ - commands::{ - Command, CommandError, - arguments::{Arg, Argument, ArgumentParsingError, NeedyGreedyString, ParseArguments}, - context::CommandContext, - permissions::Permission, - }, - runtime::Runtime, - }, - groups::moderation::{ - arguments::{DurationArgument, UserArgument}, - get_incident_page, - permissions::{BotModeratorPermission, BotOwnerPermission}, - }, - state::State, - utils::get_random_u64, +use crate::database::models::{UserIncident, UserIncidentType}; +use crate::extras::Extras; +use crate::framework::commands::arguments::{ + Arg, Argument, ArgumentParsingError, NeedyGreedyString, ParseArguments, }; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::framework::commands::{Command, CommandError}; +use crate::framework::runtime::Runtime; +use crate::groups::moderation::arguments::{DurationArgument, UserArgument}; +use crate::groups::moderation::get_incident_page; +use crate::groups::moderation::permissions::{BotModeratorPermission, BotOwnerPermission}; +use crate::state::State; +use crate::utils::get_random_u64; #[derive(Debug)] pub struct ModeratorCommand; diff --git a/src/groups/moderation/components.rs b/src/groups/moderation/components.rs index f4a72c4..769a41f 100644 --- a/src/groups/moderation/components.rs +++ b/src/groups/moderation/components.rs @@ -1,14 +1,14 @@ use async_trait::async_trait; use twilight_model::channel::message::component::ButtonStyle; -use crate::{ - database::models::UserIncident, - extras::Extras, - framework::interactions::{InteractionError, components::Button, context::InteractionContext}, - groups::moderation::get_incident_page, - state::State, - utils::get_random_u64, -}; +use crate::database::models::UserIncident; +use crate::extras::Extras; +use crate::framework::interactions::InteractionError; +use crate::framework::interactions::components::Button; +use crate::framework::interactions::context::InteractionContext; +use crate::groups::moderation::get_incident_page; +use crate::state::State; +use crate::utils::get_random_u64; #[derive(Debug)] pub struct ArrowIncidentButton { diff --git a/src/groups/moderation/embeds.rs b/src/groups/moderation/embeds.rs index 11efe7d..0dbc5e9 100644 --- a/src/groups/moderation/embeds.rs +++ b/src/groups/moderation/embeds.rs @@ -4,11 +4,9 @@ use twilight_util::builder::embed::{ EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, ImageSource, }; -use crate::{ - constants::{COLOR_RED, COLOR_YELLOW}, - database::models::{UserIncident, UserIncidentType}, - utils::ellipse, -}; +use crate::constants::{COLOR_RED, COLOR_YELLOW}; +use crate::database::models::{UserIncident, UserIncidentType}; +use crate::utils::ellipse; fn format_timestamp(timestamp: UtcDateTime) -> String { format!( diff --git a/src/groups/moderation/mod.rs b/src/groups/moderation/mod.rs index 40fda49..0031d55 100644 --- a/src/groups/moderation/mod.rs +++ b/src/groups/moderation/mod.rs @@ -1,17 +1,13 @@ use scylla::client::session::Session; -use crate::{ - database::models::{DatabaseError, UserIncident, UserIncidentType}, - extras::Extras, - framework::messages::MessageBuilder, - groups::moderation::{ - components::{ - ArrowIncidentButton, CounterButton, DeleteIncidentButton, ExpireIncidentButton, - }, - embeds::incident_embed, - }, - state::State, +use crate::database::models::{DatabaseError, UserIncident, UserIncidentType}; +use crate::extras::Extras; +use crate::framework::messages::MessageBuilder; +use crate::groups::moderation::components::{ + ArrowIncidentButton, CounterButton, DeleteIncidentButton, ExpireIncidentButton, }; +use crate::groups::moderation::embeds::incident_embed; +use crate::state::State; pub mod arguments; pub mod commands; diff --git a/src/groups/moderation/permissions.rs b/src/groups/moderation/permissions.rs index d46a276..70bb7a8 100644 --- a/src/groups/moderation/permissions.rs +++ b/src/groups/moderation/permissions.rs @@ -1,10 +1,10 @@ use async_trait::async_trait; -use crate::{ - extras::Extras, - framework::commands::{CommandError, context::CommandContext, permissions::Permission}, - state::State, -}; +use crate::extras::Extras; +use crate::framework::commands::CommandError; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::state::State; #[derive(Debug)] pub struct BotOwnerPermission; diff --git a/src/groups/servers/commands.rs b/src/groups/servers/commands.rs index b98c3be..dcc7dde 100644 --- a/src/groups/servers/commands.rs +++ b/src/groups/servers/commands.rs @@ -1,19 +1,15 @@ use async_trait::async_trait; -use crate::{ - constants::DEFAULT_PREFIX, - database::models::Server, - discord::sanitizing::SanitizeDiscordMarkdown, - extras::Extras, - framework::commands::{ - Command, CommandError, - arguments::{Arg, Argument, GreedyString, ParseArguments}, - context::CommandContext, - permissions::Permission, - }, - groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission}, - state::State, -}; +use crate::constants::DEFAULT_PREFIX; +use crate::database::models::Server; +use crate::discord::sanitizing::SanitizeDiscordMarkdown; +use crate::extras::Extras; +use crate::framework::commands::arguments::{Arg, Argument, GreedyString, ParseArguments}; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::framework::commands::{Command, CommandError}; +use crate::groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission}; +use crate::state::State; #[derive(Debug)] pub struct PrefixCommand; diff --git a/src/groups/servers/events.rs b/src/groups/servers/events.rs index 312b29f..0b6024c 100644 --- a/src/groups/servers/events.rs +++ b/src/groups/servers/events.rs @@ -5,17 +5,13 @@ use std::sync::Arc; use async_trait::async_trait; use twilight_model::gateway::payload::incoming::{GuildCreate, GuildDelete, GuildUpdate}; -use crate::{ - database::models::Server, - extras::Extras, - framework::{ - events::traits::{ - EventResult, OnServerCreateHandler, OnServerDeleteHandler, OnServerUpdateHandler, - }, - runtime::Runtime, - }, - state::State, +use crate::database::models::Server; +use crate::extras::Extras; +use crate::framework::events::traits::{ + EventResult, OnServerCreateHandler, OnServerDeleteHandler, OnServerUpdateHandler, }; +use crate::framework::runtime::Runtime; +use crate::state::State; /// Adds servers to the DB when they get created. #[derive(Debug)] diff --git a/src/groups/servers/permissions.rs b/src/groups/servers/permissions.rs index 90baf05..c11e94a 100644 --- a/src/groups/servers/permissions.rs +++ b/src/groups/servers/permissions.rs @@ -1,15 +1,14 @@ use async_trait::async_trait; -use twilight_model::{ - guild::Permissions, - id::{Id, marker::RoleMarker}, -}; +use twilight_model::guild::Permissions; +use twilight_model::id::Id; +use twilight_model::id::marker::RoleMarker; use twilight_util::permission_calculator::PermissionCalculator; -use crate::{ - extras::Extras, - framework::commands::{CommandError, context::CommandContext, permissions::Permission}, - state::State, -}; +use crate::extras::Extras; +use crate::framework::commands::CommandError; +use crate::framework::commands::context::CommandContext; +use crate::framework::commands::permissions::Permission; +use crate::state::State; #[derive(Debug)] pub struct GuildOnlyPermission; diff --git a/src/logging.rs b/src/logging.rs index 84863e4..a95ef65 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -39,7 +39,8 @@ use colored::Colorize; use log::{Level, LevelFilter}; -use time::{OffsetDateTime, macros::format_description}; +use time::OffsetDateTime; +use time::macros::format_description; pub struct Logger; diff --git a/src/main.rs b/src/main.rs index bc98053..1c1f001 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,62 +100,49 @@ //! $ cargo run -- docs --open //! ``` -use std::{ - error::Error, - net::{IpAddr, Ipv4Addr, SocketAddr}, - path::Path, - process::Command, - sync::Arc, -}; +use std::error::Error; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::path::Path; +use std::process::Command; +use std::sync::Arc; use async_trait::async_trait; -use axum::{Router, response::Redirect, routing}; +use axum::response::Redirect; +use axum::{Router, routing}; use clap::Parser; use notify::{Event, EventKind, Watcher}; use tokio::net::TcpListener; use tower_http::services::ServeDir; use twilight_cache_inmemory::ResourceType; use twilight_gateway::{Intents, ShardId}; -use twilight_model::{ - gateway::{ - payload::outgoing::update_presence::UpdatePresencePayload, - presence::{Activity, ActivityType, Status}, - }, - user::User as TwilightUser, -}; +use twilight_model::user::User as TwilightUser; -use crate::{ - cli::{Args, Commands}, - colors::init_color_fetching_queue_listener, - constants::{DEFAULT_PREFIX, VERSION}, - database::models::{Server, User}, - extras::Extras, - framework::{ - bot::{Bot, PrefixGetter}, - commands::extras::ExtrasGetter, - runtime::Runtime, - }, - groups::{ - about::{ - commands::{HelpCommand, TetoCommand}, - components::HelpGoBackButton, tasks::UpdatePresenceTask, - }, - calls::{ - commands::{ - CallCommand, FriendRequestCommand, HangupCommand, MaskCommand, SkipCommand, - }, - events::{CallMessageDeleteHandler, CallMessageEditHandler, CallMessageHandler}, - }, - gateway::events::{ReadyHandler, ReconnectHandler}, - leveling::{commands::LeaderboardCommand, events::MessageLevelingHandler}, - moderation::commands::{BanCommand, IncidentsCommand, ModeratorCommand}, - servers::{ - commands::PrefixCommand, - events::{ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler}, - }, - }, - state::State, +use crate::cli::{Args, Commands}; +use crate::colors::init_color_fetching_queue_listener; +use crate::constants::{DEFAULT_PREFIX, VERSION}; +use crate::database::models::{Server, User}; +use crate::extras::Extras; +use crate::framework::bot::{Bot, PrefixGetter}; +use crate::framework::commands::extras::ExtrasGetter; +use crate::framework::runtime::Runtime; +use crate::groups::about::commands::{CodeOfConductCommand, DonateCommand, HelpCommand}; +use crate::groups::about::components::HelpGoBackButton; +use crate::groups::about::tasks::UpdatePresenceTask; +use crate::groups::calls::commands::{ + CallCommand, FriendRequestCommand, HangupCommand, MaskCommand, SkipCommand, }; +use crate::groups::calls::events::{ + CallMessageDeleteHandler, CallMessageEditHandler, CallMessageHandler, +}; +use crate::groups::gateway::events::{ReadyHandler, ReconnectHandler}; +use crate::groups::leveling::commands::LeaderboardCommand; +use crate::groups::leveling::events::MessageLevelingHandler; +use crate::groups::moderation::commands::{BanCommand, IncidentsCommand, ModeratorCommand}; +use crate::groups::servers::commands::PrefixCommand; +use crate::groups::servers::events::{ + ServerCreateHandler, ServerDeleteHandler, ServerUpdateHandler, +}; +use crate::state::State; mod cli; mod colors; @@ -299,7 +286,8 @@ async fn start( .intent(Intents::GUILD_MESSAGES) .intent(Intents::MESSAGE_CONTENT) .command(HelpCommand) - .command(TetoCommand) + .command(CodeOfConductCommand) + .command(DonateCommand) .command(CallCommand::new()) .command(SkipCommand) .command(HangupCommand) diff --git a/src/redis.rs b/src/redis.rs index e028e21..9fab3da 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -1,7 +1,5 @@ -use deadpool_redis::{ - Runtime, - cluster::{Config, CreatePoolError, Pool}, -}; +use deadpool_redis::Runtime; +use deadpool_redis::cluster::{Config, CreatePoolError, Pool}; pub fn init(urls: Vec) -> Result { Config::from_urls(urls).create_pool(Some(Runtime::Tokio1)) diff --git a/src/utils.rs b/src/utils.rs index 2eea93e..116672e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -133,3 +133,25 @@ pub fn split_at_nth_from_end<'a, 'b>( None } + +/// Formats a number with commas as thousands separators. +/// +/// E.g. `123456789` becomes `123,456,789`. +/// +/// Arguments: +/// * `num` - The number to format. +/// +/// Returns: +/// [`String`] - The formatted number as a string. +pub fn format_thousands(num: u64) -> String { + let mut num_str = num.to_string(); + let mut result = String::new(); + + while num_str.len() > 3 { + let chunk = &num_str[num_str.len() - 3..]; + result = format!(",{chunk}{result}"); + num_str.truncate(num_str.len() - 3); + } + + format!("{}{}", num_str, result) +}