Add support for extras, conditionally displaying commands in the help command, and start working on moderation commands
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
extras::Extras,
|
||||
framework::commands::{
|
||||
Command, CommandError,
|
||||
arguments::{Argument, NeedyString, ParseArguments},
|
||||
arguments::{Arg, Argument, NeedyString, ParseArguments},
|
||||
context::CommandContext,
|
||||
},
|
||||
groups::about::{
|
||||
@@ -19,7 +20,7 @@ use crate::{
|
||||
pub struct HelpCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for HelpCommand {
|
||||
impl Command<State, Extras> for HelpCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"help"
|
||||
}
|
||||
@@ -38,12 +39,12 @@ impl Command<State> for HelpCommand {
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
args: &str,
|
||||
) -> 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) {
|
||||
ctx.send()
|
||||
.embed(get_command_help_embed(&command, &ctx))
|
||||
@@ -54,7 +55,7 @@ impl Command<State> for HelpCommand {
|
||||
.map_err(|_| CommandError::MessageSendingFailed)?;
|
||||
} else {
|
||||
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(HelpSupportLinkButton, 1)
|
||||
.await
|
||||
@@ -62,7 +63,7 @@ impl Command<State> for HelpCommand {
|
||||
}
|
||||
} else {
|
||||
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(HelpSupportLinkButton, 1)
|
||||
.component(HelpDonateLinkButton, 1)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
extras::Extras,
|
||||
framework::interactions::{InteractionError, components::Button, context::InteractionContext},
|
||||
groups::about::embeds::get_all_command_help_embed,
|
||||
state::State,
|
||||
@@ -10,19 +11,22 @@ use crate::{
|
||||
pub struct HelpGoBackButton;
|
||||
|
||||
#[async_trait]
|
||||
impl Button<State> for HelpGoBackButton {
|
||||
impl Button<State, Extras> for HelpGoBackButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
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
|
||||
.original_message()
|
||||
.expect(
|
||||
"Buttons always have the original message in the interaction context, this one didn't (got None)"
|
||||
)
|
||||
.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(HelpSupportLinkButton, 1)
|
||||
.component(HelpDonateLinkButton, 1)
|
||||
@@ -41,7 +45,7 @@ impl Button<State> for HelpGoBackButton {
|
||||
pub struct HelpSupportLinkButton;
|
||||
|
||||
#[async_trait]
|
||||
impl Button<State> for HelpSupportLinkButton {
|
||||
impl Button<State, Extras> for HelpSupportLinkButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Get help")
|
||||
}
|
||||
@@ -55,7 +59,7 @@ impl Button<State> for HelpSupportLinkButton {
|
||||
pub struct HelpWebsiteLinkButton;
|
||||
|
||||
#[async_trait]
|
||||
impl Button<State> for HelpWebsiteLinkButton {
|
||||
impl Button<State, Extras> for HelpWebsiteLinkButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Website")
|
||||
}
|
||||
@@ -69,7 +73,7 @@ impl Button<State> for HelpWebsiteLinkButton {
|
||||
pub struct HelpDonateLinkButton;
|
||||
|
||||
#[async_trait]
|
||||
impl Button<State> for HelpDonateLinkButton {
|
||||
impl Button<State, Extras> for HelpDonateLinkButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Donate")
|
||||
}
|
||||
@@ -83,7 +87,7 @@ impl Button<State> for HelpDonateLinkButton {
|
||||
pub struct CodeOfConductLinkButton;
|
||||
|
||||
#[async_trait]
|
||||
impl Button<State> for CodeOfConductLinkButton {
|
||||
impl Button<State, Extras> for CodeOfConductLinkButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Code of Conduct")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use twilight_util::builder::embed::{
|
||||
use crate::{
|
||||
constants::{COLOR_RED, COLOR_WHITE},
|
||||
discord::{images, sanitizing::SanitizeDiscordMarkdown},
|
||||
extras::Extras,
|
||||
framework::{
|
||||
commands::{Command, context::CommandContext},
|
||||
runtime::Runtime,
|
||||
@@ -17,8 +18,8 @@ use crate::{
|
||||
};
|
||||
|
||||
pub(super) fn get_command_help_embed(
|
||||
command: &Arc<dyn Command<State>>,
|
||||
ctx: &CommandContext<'_, State>,
|
||||
command: &Arc<dyn Command<State, Extras>>,
|
||||
ctx: &CommandContext<'_, State, Extras>,
|
||||
) -> Embed {
|
||||
let name = format!("{}{}", ctx.prefix, command.name());
|
||||
let arguments = command
|
||||
@@ -79,7 +80,11 @@ pub(super) fn get_command_help_embed(
|
||||
.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()
|
||||
.author(EmbedAuthorBuilder::new(rt.app.name.clone()).icon_url(ImageSource::url(images::get_avatar_url(
|
||||
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())
|
||||
.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)))
|
||||
.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();
|
||||
|
||||
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```")
|
||||
}
|
||||
|
||||
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()
|
||||
.color(COLOR_RED)
|
||||
.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::{
|
||||
database::models::{Call, Channel},
|
||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||
extras::Extras,
|
||||
framework::commands::{
|
||||
Command, CommandError, context::CommandContext, permissions::Permission,
|
||||
},
|
||||
@@ -39,7 +40,7 @@ impl CallCommand {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for CallCommand {
|
||||
impl Command<State, Extras> for CallCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"call"
|
||||
}
|
||||
@@ -48,13 +49,13 @@ impl Command<State> for CallCommand {
|
||||
&["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)]
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
_args: &str,
|
||||
) -> Result<(), CommandError> {
|
||||
let channel = Channel::get_or_create_by_id(
|
||||
@@ -66,7 +67,7 @@ impl Command<State> for CallCommand {
|
||||
.inspect_err(|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()) {
|
||||
ctx.send()
|
||||
@@ -100,7 +101,7 @@ impl Command<State> for CallCommand {
|
||||
log::error!("Failed to start call: {e:?}");
|
||||
self.channel_lock.remove(&ctx.event.channel_id.get());
|
||||
})
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
self.channel_lock
|
||||
.remove_async(&ctx.event.channel_id.get())
|
||||
@@ -141,7 +142,7 @@ impl Command<State> for CallCommand {
|
||||
pub struct SkipCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for SkipCommand {
|
||||
impl Command<State, Extras> for SkipCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"skip"
|
||||
}
|
||||
@@ -150,13 +151,13 @@ impl Command<State> for SkipCommand {
|
||||
&["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)]
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
_args: &str,
|
||||
) -> Result<(), CommandError> {
|
||||
let channel = Channel::get_or_create_by_id(
|
||||
@@ -168,7 +169,7 @@ impl Command<State> for SkipCommand {
|
||||
.inspect_err(|e| {
|
||||
log::error!("Failed to get or create channel: {e:?}");
|
||||
})
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
if channel.call_id.is_none() {
|
||||
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)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
||||
.map_err(|_| CommandError::DatabaseFailed)?
|
||||
.ok_or(CommandError::DatabaseInconsistentState)?;
|
||||
|
||||
call.hangup(&ctx.rt.state.db)
|
||||
.await
|
||||
.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());
|
||||
|
||||
@@ -225,7 +226,7 @@ impl Command<State> for SkipCommand {
|
||||
.inspect_err(|e| {
|
||||
log::error!("Failed to start (skipped) call: {e:?}");
|
||||
})
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
if let Some(call) = call {
|
||||
ctx.send()
|
||||
@@ -259,7 +260,7 @@ impl Command<State> for SkipCommand {
|
||||
pub struct HangupCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for HangupCommand {
|
||||
impl Command<State, Extras> for HangupCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"hangup"
|
||||
}
|
||||
@@ -268,19 +269,19 @@ impl Command<State> for HangupCommand {
|
||||
&["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)]
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
_args: &str,
|
||||
) -> Result<(), CommandError> {
|
||||
let Some(channel) = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
||||
.map_err(|_| CommandError::DatabaseFailed)?
|
||||
else {
|
||||
ctx.send()
|
||||
.content(format!(
|
||||
@@ -300,7 +301,7 @@ impl Command<State> for HangupCommand {
|
||||
.inspect_err(|e| {
|
||||
log::error!("Failed to cancel search: {e:?}");
|
||||
})
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
ctx.send()
|
||||
.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)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
||||
.map_err(|_| CommandError::DatabaseFailed)?
|
||||
else {
|
||||
ctx.send()
|
||||
.content("❌ Call not found.")
|
||||
@@ -327,7 +328,7 @@ impl Command<State> for HangupCommand {
|
||||
call.hangup(&ctx.rt.state.db)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
ctx.send()
|
||||
.content(format!(
|
||||
@@ -366,7 +367,7 @@ impl Command<State> for HangupCommand {
|
||||
pub struct FriendRequestCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for FriendRequestCommand {
|
||||
impl Command<State, Extras> for FriendRequestCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"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."
|
||||
}
|
||||
|
||||
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)]
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
_args: &str,
|
||||
) -> Result<(), CommandError> {
|
||||
let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get channel: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
let Some(channel) = channel else {
|
||||
ctx.send()
|
||||
@@ -409,7 +410,7 @@ impl Command<State> for FriendRequestCommand {
|
||||
Call::get_by_id(call_id, &ctx.rt.state.db)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
||||
.map_err(|_| CommandError::DatabaseFailed)?
|
||||
} else {
|
||||
ctx.send()
|
||||
.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)]
|
||||
pub struct CodeOfConductButton;
|
||||
|
||||
impl Button<State> for CodeOfConductButton {
|
||||
impl Button<State, Extras> for CodeOfConductButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Code of Conduct")
|
||||
}
|
||||
@@ -16,7 +16,7 @@ impl Button<State> for CodeOfConductButton {
|
||||
#[derive(Debug)]
|
||||
pub struct NeedHelpButton;
|
||||
|
||||
impl Button<State> for NeedHelpButton {
|
||||
impl Button<State, Extras> for NeedHelpButton {
|
||||
fn label(&self) -> Option<&'static str> {
|
||||
Some("Need Help?")
|
||||
}
|
||||
|
||||
+26
-17
@@ -11,21 +11,18 @@ use twilight_util::builder::embed::{EmbedAuthorBuilder, EmbedBuilder, ImageSourc
|
||||
|
||||
use crate::{
|
||||
constants::COLOR_WHITE,
|
||||
database::models::{
|
||||
Call, Channel, Message, User, UserIncident, get_or_create_user_and_set_last_seen,
|
||||
},
|
||||
database::models::{Call, Channel, Message, User, UserIncident},
|
||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||
extras::Extras,
|
||||
framework::{
|
||||
events::traits::{
|
||||
commands::extras::ExtrasGetter, events::traits::{
|
||||
EventResult, OnMessageDeleteHandler, OnMessageEditHandler, OnMessageHandler,
|
||||
},
|
||||
interactions::components::ActionRow,
|
||||
runtime::Runtime,
|
||||
}, interactions::components::ActionRow, runtime::Runtime
|
||||
},
|
||||
groups::calls::{
|
||||
components::{CodeOfConductButton, NeedHelpButton},
|
||||
embeds, get_display_profile,
|
||||
moderation::{Action, Rule, escape_message},
|
||||
moderation::{escape_message, Action, Rule},
|
||||
},
|
||||
state::State,
|
||||
utils::{ellipse, i64_to_u64},
|
||||
@@ -35,7 +32,7 @@ async fn penalize_user(
|
||||
user: User,
|
||||
message: Message,
|
||||
broken_rules: Vec<Arc<dyn Rule>>,
|
||||
rt: &Runtime<State>,
|
||||
rt: &Runtime<State, Extras>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let reason = format!(
|
||||
"You broke the following rules during a call:\n{}",
|
||||
@@ -88,8 +85,13 @@ async fn penalize_user(
|
||||
pub struct CallMessageHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnMessageHandler<State> for CallMessageHandler {
|
||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnMessageHandler<State, Extras> for CallMessageHandler {
|
||||
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) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -144,7 +146,8 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
||||
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)
|
||||
.await?
|
||||
@@ -314,8 +317,12 @@ impl OnMessageHandler<State> for CallMessageHandler {
|
||||
pub struct CallMessageEditHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
||||
async fn on_message_edit(&self, event: &MessageUpdate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnMessageEditHandler<State, Extras> for CallMessageEditHandler {
|
||||
async fn on_message_edit(
|
||||
&self,
|
||||
event: &MessageUpdate,
|
||||
rt: Arc<Runtime<State, Extras>>,
|
||||
) -> EventResult {
|
||||
log::trace!(
|
||||
"Edited message was '{}' sent by {} (bot? {:?})",
|
||||
event.content,
|
||||
@@ -368,7 +375,9 @@ impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
||||
.reply(event.id)
|
||||
.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?;
|
||||
|
||||
@@ -404,11 +413,11 @@ impl OnMessageEditHandler<State> for CallMessageEditHandler {
|
||||
pub struct CallMessageDeleteHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnMessageDeleteHandler<State> for CallMessageDeleteHandler {
|
||||
impl OnMessageDeleteHandler<State, Extras> for CallMessageDeleteHandler {
|
||||
async fn on_message_delete(
|
||||
&self,
|
||||
event: &MessageDelete,
|
||||
rt: Arc<Runtime<State>>,
|
||||
rt: Arc<Runtime<State, Extras>>,
|
||||
) -> EventResult {
|
||||
let Some(channel) = Channel::get_by_id(event.channel_id.get(), &rt.state.db).await? else {
|
||||
return Ok(());
|
||||
|
||||
@@ -80,6 +80,8 @@ gooncream
|
||||
gooncreaming
|
||||
goonbud
|
||||
goonbuds
|
||||
goonete
|
||||
goonetes
|
||||
boty hole
|
||||
suck my pp
|
||||
ass cracker
|
||||
@@ -98,7 +100,7 @@ masterbaiting
|
||||
masturbatigng
|
||||
horny
|
||||
hrny
|
||||
whore
|
||||
whore / who re
|
||||
whre
|
||||
tickle your pickle
|
||||
tickle yr pickle
|
||||
|
||||
@@ -2,6 +2,7 @@ use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
database::models::UserIncident,
|
||||
extras::Extras,
|
||||
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
||||
groups::calls::components::{CodeOfConductButton, NeedHelpButton},
|
||||
state::State,
|
||||
@@ -11,12 +12,12 @@ use crate::{
|
||||
pub struct NotBannedPermission;
|
||||
|
||||
#[async_trait]
|
||||
impl Permission<State> for NotBannedPermission {
|
||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
||||
impl Permission<State, Extras> for NotBannedPermission {
|
||||
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)
|
||||
.await
|
||||
.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()) {
|
||||
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()
|
||||
.content("❌ You've been banned from Payphone.")
|
||||
.component(CodeOfConductButton, 1)
|
||||
|
||||
@@ -6,6 +6,7 @@ use async_trait::async_trait;
|
||||
use twilight_model::gateway::payload::incoming::Ready;
|
||||
|
||||
use crate::{
|
||||
extras::Extras,
|
||||
framework::{
|
||||
events::traits::{EventResult, OnReadyHandler, OnReconnectHandler},
|
||||
runtime::Runtime,
|
||||
@@ -18,8 +19,8 @@ use crate::{
|
||||
pub struct ReadyHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnReadyHandler<State> for ReadyHandler {
|
||||
async fn on_ready(&self, event: &Ready, _: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnReadyHandler<State, Extras> for ReadyHandler {
|
||||
async fn on_ready(&self, event: &Ready, _: Arc<Runtime<State, Extras>>) -> EventResult {
|
||||
log::info!(
|
||||
"Ready! Logged in as {}#{:>04}",
|
||||
event.user.name,
|
||||
@@ -35,8 +36,8 @@ impl OnReadyHandler<State> for ReadyHandler {
|
||||
pub struct ReconnectHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnReconnectHandler<State> for ReconnectHandler {
|
||||
async fn on_reconnect(&self, _: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnReconnectHandler<State, Extras> for ReconnectHandler {
|
||||
async fn on_reconnect(&self, _: Arc<Runtime<State, Extras>>) -> EventResult {
|
||||
log::info!("The gateway asked for a reconnection");
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -6,19 +6,14 @@ use twilight_util::builder::embed::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
constants::COLOR_WHITE,
|
||||
database::models::User,
|
||||
discord::{images, sanitizing::SanitizeDiscordMarkdown},
|
||||
framework::commands::{Command, CommandError, context::CommandContext},
|
||||
groups::leveling::constants::KEY_LEADERBOARD_MONTHLY,
|
||||
state::State,
|
||||
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
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LeaderboardCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for LeaderboardCommand {
|
||||
impl Command<State, Extras> for LeaderboardCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"leaderboard"
|
||||
}
|
||||
@@ -33,7 +28,7 @@ impl Command<State> for LeaderboardCommand {
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
_args: &str,
|
||||
) -> Result<(), CommandError> {
|
||||
let mut redis = ctx
|
||||
@@ -61,7 +56,7 @@ impl Command<State> for LeaderboardCommand {
|
||||
for (user_id, score) in top {
|
||||
let Some(user) = User::get_by_id(user_id, &ctx.rt.state.db)
|
||||
.await
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?
|
||||
.map_err(|_| CommandError::DatabaseFailed)?
|
||||
else {
|
||||
log::trace!("User ID {user_id} not found for leaderboard");
|
||||
continue;
|
||||
|
||||
@@ -8,10 +8,13 @@ use time::{Date, Month, OffsetDateTime, Time};
|
||||
use twilight_model::gateway::payload::incoming::MessageCreate;
|
||||
|
||||
use crate::{
|
||||
database::models::Channel, framework::{
|
||||
events::traits::{EventResult, OnMessageHandler},
|
||||
runtime::Runtime,
|
||||
}, groups::leveling::constants::KEY_LEADERBOARD_MONTHLY, state::State
|
||||
database::models::Channel,
|
||||
extras::Extras,
|
||||
framework::{
|
||||
commands::extras::ExtrasGetter, events::traits::{EventResult, OnMessageHandler}, runtime::Runtime
|
||||
},
|
||||
groups::leveling::constants::KEY_LEADERBOARD_MONTHLY,
|
||||
state::State,
|
||||
};
|
||||
|
||||
fn seconds_until_end_of_month() -> i64 {
|
||||
@@ -38,8 +41,13 @@ fn seconds_until_end_of_month() -> i64 {
|
||||
pub struct MessageLevelingHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnMessageHandler<State> for MessageLevelingHandler {
|
||||
async fn on_message(&self, event: &MessageCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnMessageHandler<State, Extras> for MessageLevelingHandler {
|
||||
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) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -72,7 +80,9 @@ impl OnMessageHandler<State> for MessageLevelingHandler {
|
||||
let lock = redis.ttl(&key).await?;
|
||||
|
||||
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(());
|
||||
}
|
||||
|
||||
@@ -92,7 +102,9 @@ impl OnMessageHandler<State> for MessageLevelingHandler {
|
||||
let leaderboard_exat = redis.ttl(KEY_LEADERBOARD_MONTHLY).await?;
|
||||
|
||||
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(())
|
||||
|
||||
@@ -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 crate::{
|
||||
constants::DEFAULT_PREFIX,
|
||||
database::models::Server,
|
||||
discord::sanitizing::SanitizeDiscordMarkdown,
|
||||
framework::commands::{
|
||||
Command, CommandError,
|
||||
arguments::{Argument, GreedyString, ParseArguments},
|
||||
context::CommandContext,
|
||||
permissions::Permission,
|
||||
},
|
||||
groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission},
|
||||
state::State,
|
||||
constants::DEFAULT_PREFIX, database::models::Server, discord::sanitizing::SanitizeDiscordMarkdown, extras::Extras, framework::commands::{
|
||||
arguments::{Arg, Argument, GreedyString, ParseArguments}, context::CommandContext, permissions::Permission, Command, CommandError
|
||||
}, groups::servers::permissions::{GuildOnlyPermission, ServerModeratorPermission}, state::State
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrefixCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl Command<State> for PrefixCommand {
|
||||
impl Command<State, Extras> for PrefixCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"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![
|
||||
Box::new(GuildOnlyPermission),
|
||||
Box::new(ServerModeratorPermission),
|
||||
@@ -48,10 +40,10 @@ impl Command<State> for PrefixCommand {
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
ctx: CommandContext<'_, State>,
|
||||
ctx: CommandContext<'_, State, Extras>,
|
||||
args: &str,
|
||||
) -> 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 server = Server::get_or_create_by_id(
|
||||
@@ -64,7 +56,7 @@ impl Command<State> for PrefixCommand {
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| log::error!("Failed to get or create server: {e}"))
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
if prefix.is_empty() {
|
||||
prefix = DEFAULT_PREFIX;
|
||||
@@ -74,7 +66,7 @@ impl Command<State> for PrefixCommand {
|
||||
server
|
||||
.upsert(&ctx.rt.state.db)
|
||||
.await
|
||||
.map_err(|_| CommandError::DatabaseQueryFailed)?;
|
||||
.map_err(|_| CommandError::DatabaseFailed)?;
|
||||
|
||||
ctx.send()
|
||||
.content(format!(
|
||||
|
||||
@@ -7,6 +7,7 @@ use twilight_model::gateway::payload::incoming::{GuildCreate, GuildDelete, Guild
|
||||
|
||||
use crate::{
|
||||
database::models::Server,
|
||||
extras::Extras,
|
||||
framework::{
|
||||
events::traits::{
|
||||
EventResult, OnServerCreateHandler, OnServerDeleteHandler, OnServerUpdateHandler,
|
||||
@@ -21,8 +22,12 @@ use crate::{
|
||||
pub struct ServerCreateHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnServerCreateHandler<State> for ServerCreateHandler {
|
||||
async fn on_server_create(&self, event: &GuildCreate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnServerCreateHandler<State, Extras> for ServerCreateHandler {
|
||||
async fn on_server_create(
|
||||
&self,
|
||||
event: &GuildCreate,
|
||||
rt: Arc<Runtime<State, Extras>>,
|
||||
) -> EventResult {
|
||||
let server = match event {
|
||||
GuildCreate::Available(guild) => Server::from(guild.clone()),
|
||||
GuildCreate::Unavailable(_) => {
|
||||
@@ -51,8 +56,12 @@ impl OnServerCreateHandler<State> for ServerCreateHandler {
|
||||
pub struct ServerUpdateHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnServerUpdateHandler<State> for ServerUpdateHandler {
|
||||
async fn on_server_update(&self, event: &GuildUpdate, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnServerUpdateHandler<State, Extras> for ServerUpdateHandler {
|
||||
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)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
@@ -83,8 +92,12 @@ impl OnServerUpdateHandler<State> for ServerUpdateHandler {
|
||||
pub struct ServerDeleteHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl OnServerDeleteHandler<State> for ServerDeleteHandler {
|
||||
async fn on_server_delete(&self, event: &GuildDelete, rt: Arc<Runtime<State>>) -> EventResult {
|
||||
impl OnServerDeleteHandler<State, Extras> for ServerDeleteHandler {
|
||||
async fn on_server_delete(
|
||||
&self,
|
||||
event: &GuildDelete,
|
||||
rt: Arc<Runtime<State, Extras>>,
|
||||
) -> EventResult {
|
||||
Server::delete_by_id(event.id.get(), &rt.state.db)
|
||||
.await
|
||||
.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 crate::{
|
||||
extras::Extras,
|
||||
framework::commands::{CommandError, context::CommandContext, permissions::Permission},
|
||||
state::State,
|
||||
};
|
||||
@@ -14,12 +15,15 @@ use crate::{
|
||||
pub struct GuildOnlyPermission;
|
||||
|
||||
#[async_trait]
|
||||
impl Permission<State> for GuildOnlyPermission {
|
||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
||||
impl Permission<State, Extras> for GuildOnlyPermission {
|
||||
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||
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()
|
||||
.content("❌ This command can only be used in a server.")
|
||||
.await?;
|
||||
@@ -32,8 +36,8 @@ impl Permission<State> for GuildOnlyPermission {
|
||||
pub struct ServerModeratorPermission;
|
||||
|
||||
#[async_trait]
|
||||
impl Permission<State> for ServerModeratorPermission {
|
||||
async fn check(&self, ctx: &CommandContext<State>) -> Result<bool, CommandError> {
|
||||
impl Permission<State, Extras> for ServerModeratorPermission {
|
||||
async fn check(&self, ctx: &CommandContext<State, Extras>) -> Result<bool, CommandError> {
|
||||
if let Some(guild_id) = ctx.event.guild_id {
|
||||
let Some(guild) = ctx.rt.cache.guild(guild_id) else {
|
||||
log::warn!("Could not get guild for ID {guild_id}!");
|
||||
@@ -76,14 +80,19 @@ impl Permission<State> for ServerModeratorPermission {
|
||||
)
|
||||
.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.
|
||||
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()
|
||||
.content("❌ This command can only be run by a server moderator.\n-# 🛈 Moderators must have permission to manage messages and to manage channels.")
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user