363 lines
11 KiB
Rust
363 lines
11 KiB
Rust
use async_trait::async_trait;
|
|
use deadpool_redis::redis::AsyncTypedCommands;
|
|
use time::UtcDateTime;
|
|
|
|
use crate::database::models::{Call, 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::{ChannelArgument, DurationArgument, UserArgument};
|
|
use crate::groups::moderation::get_incident_page;
|
|
use crate::groups::moderation::permissions::{BotModeratorPermission, BotOwnerPermission};
|
|
use crate::state::State;
|
|
use crate::utils::{Bye, get_random_u64};
|
|
|
|
#[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?;
|
|
return Ok(());
|
|
}
|
|
ArgumentParsingError::DatabaseFailed(e) => {
|
|
return Err(CommandError::DatabaseFailed(e));
|
|
}
|
|
e => {
|
|
unreachable!("Unexpected error: {e}");
|
|
}
|
|
},
|
|
};
|
|
|
|
user.is_moderator = !user.is_moderator;
|
|
|
|
user.insert(&ctx.rt.state.db).await?;
|
|
|
|
ctx.send()
|
|
.content(format!(
|
|
"👍 Done. <@{}> {}",
|
|
user.id,
|
|
if user.is_moderator {
|
|
"**is now** a moderator"
|
|
} else {
|
|
"**is no longer** a moderator"
|
|
}
|
|
))
|
|
.await?;
|
|
|
|
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 arguments(&self) -> &'static [Argument] {
|
|
&[Argument {
|
|
name: "user",
|
|
is_optional: false,
|
|
is_greedy: false,
|
|
}]
|
|
}
|
|
|
|
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?;
|
|
return Ok(());
|
|
}
|
|
ArgumentParsingError::DatabaseFailed(e) => {
|
|
return Err(CommandError::DatabaseFailed(e));
|
|
}
|
|
e => {
|
|
unreachable!("Unexpected error: {e}");
|
|
}
|
|
},
|
|
};
|
|
|
|
let message = get_incident_page(user.id, &ctx.rt.state.db, 0).await?;
|
|
|
|
ctx.send_from_builder(message).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BanCommand;
|
|
|
|
#[async_trait]
|
|
impl Command<State, Extras> for BanCommand {
|
|
fn name(&self) -> &'static str {
|
|
"ban"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["b"]
|
|
}
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Bans a user from Payphone calls.\n\nThe user can be specified via either replying to them (works on call too), mentioning them, specifying their username, specifying their ID, or not setting it to default to yourself.\n\nThe duration can be specified as defined in the [`humantime::parse_duration` documentation](https://docs.rs/humantime/latest/humantime/fn.parse_duration.html)."
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
fn arguments(&self) -> &'static [Argument] {
|
|
&[
|
|
Argument {
|
|
name: "user",
|
|
is_optional: false,
|
|
is_greedy: false,
|
|
},
|
|
Argument {
|
|
name: "duration",
|
|
is_optional: true,
|
|
is_greedy: false,
|
|
},
|
|
Argument {
|
|
name: "reason",
|
|
is_optional: false,
|
|
is_greedy: true,
|
|
},
|
|
]
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State, Extras>,
|
|
args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let (user, duration, reason) = match <(
|
|
UserArgument,
|
|
Option<DurationArgument>,
|
|
NeedyGreedyString,
|
|
)>::parse(args, &ctx)
|
|
.await
|
|
{
|
|
Ok(arg) => arg,
|
|
Err(e) => match e {
|
|
ArgumentParsingError::InvalidArgument => {
|
|
ctx.send()
|
|
.content("❌ User not found in DB or no reason was provided.")
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
ArgumentParsingError::DatabaseFailed(e) => {
|
|
return Err(CommandError::DatabaseFailed(e));
|
|
}
|
|
ArgumentParsingError::MissingArgument => {
|
|
ctx.send().content(format!("❌ No reason was provided. Use `{}help ban` to learn how to use this command.", ctx.prefix)).await?;
|
|
return Ok(());
|
|
}
|
|
},
|
|
};
|
|
|
|
let incident = UserIncident {
|
|
created_at: UtcDateTime::now(),
|
|
user_id: user.id,
|
|
incident_id: get_random_u64(),
|
|
report_id: None,
|
|
r#type: UserIncidentType::Ban,
|
|
message: None,
|
|
created_by_id: Some(ctx.event.author.id.get()),
|
|
reason: (*reason).clone(),
|
|
expires_at: duration.map(|d| UtcDateTime::now() + *d),
|
|
duration: duration.map(|d| humantime::format_duration(*d).to_string()),
|
|
};
|
|
incident.insert(&ctx.rt.state.db).await?;
|
|
|
|
ctx.send()
|
|
.content(format!(
|
|
"✅ User `{}` has been banned from Payphone{}.",
|
|
user.name,
|
|
duration
|
|
.map(|d| format!(" for {}", humantime::format_duration(*d)))
|
|
.unwrap_or_default()
|
|
))
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct RemoteHangupCommand;
|
|
|
|
#[async_trait]
|
|
impl Command<State, Extras> for RemoteHangupCommand {
|
|
fn name(&self) -> &'static str {
|
|
"remotehangup"
|
|
}
|
|
|
|
fn aliases(&self) -> &'static [&'static str] {
|
|
&["rh"]
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
fn arguments(&self) -> &'static [Argument] {
|
|
&[Argument {
|
|
name: "channel",
|
|
is_optional: false,
|
|
is_greedy: false,
|
|
}]
|
|
}
|
|
|
|
async fn execute(
|
|
&self,
|
|
ctx: CommandContext<'_, State, Extras>,
|
|
args: &str,
|
|
) -> Result<(), CommandError> {
|
|
let Arg(mut channel) = match Arg::<ChannelArgument>::parse(args, &ctx).await {
|
|
Ok(arg) => arg,
|
|
Err(e) => match e {
|
|
ArgumentParsingError::InvalidArgument => {
|
|
ctx.send().content("❌ Channel not found in DB.").await?;
|
|
return Ok(());
|
|
}
|
|
ArgumentParsingError::DatabaseFailed(e) => {
|
|
return Err(CommandError::DatabaseFailed(e));
|
|
}
|
|
e => {
|
|
unreachable!("Unexpected error: {e}");
|
|
}
|
|
},
|
|
};
|
|
|
|
if channel.is_searching {
|
|
channel
|
|
.cancel_search(&ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| {
|
|
log::error!("Failed to cancel search: {e:?}");
|
|
})?;
|
|
|
|
ctx.send()
|
|
.content("The channel was remotely made to stop searching for a call!")
|
|
.await?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(call_id) = channel.call_id {
|
|
let mut redis = ctx.rt.state.redis.get().await?;
|
|
|
|
let Some(call) = Call::get_by_id(call_id, &ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to get call: {e:?}"))?
|
|
else {
|
|
ctx.send().content("❌ The call could not be found.\n-# This is not supposed to happen. The channel is pointing to an ID in the database that does not exist.").await?;
|
|
|
|
channel.call_id = None;
|
|
channel.upsert(&ctx.rt.state.db).await.bye();
|
|
|
|
redis
|
|
.zrem("active-calls", format!("{}:{}", call_id, channel.id))
|
|
.await
|
|
.bye();
|
|
|
|
return Err(CommandError::DatabaseInconsistentState);
|
|
};
|
|
|
|
let channel_keys = [
|
|
format!("{call_id}:{}", call.channel_ids[0]),
|
|
format!("{call_id}:{}", call.channel_ids[1]),
|
|
];
|
|
|
|
redis
|
|
.zrem("active-calls", &channel_keys)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to remove inactive call from redis: {e}"))
|
|
.ok();
|
|
|
|
call.hangup(&ctx.rt.state.db)
|
|
.await
|
|
.inspect_err(|e| log::error!("Failed to hang up call: {e:?}"))?;
|
|
|
|
ctx.send().content(format!("<#{}> had its call hung up.", channel.id)).await?;
|
|
} else {
|
|
ctx.send()
|
|
.content("❌ This channel is not in a call.")
|
|
.await?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|