Add moderation to Kon and refactor unrelated parts
This commit is contained in:
parent
10dd8f7231
commit
bd2afed839
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -952,7 +952,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kon"
|
||||
version = "0.2.7"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"cargo_toml",
|
||||
"gamedig",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kon"
|
||||
version = "0.2.7"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -2,3 +2,4 @@ pub mod ping;
|
||||
pub mod status;
|
||||
pub mod uptime;
|
||||
pub mod gameserver;
|
||||
pub mod moderation;
|
||||
|
110
src/commands/moderation.rs
Normal file
110
src/commands/moderation.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use crate::{
|
||||
Error,
|
||||
internals::utils::capitalize_first,
|
||||
models::moderation_events::{
|
||||
Moderations,
|
||||
ActionTypes
|
||||
}
|
||||
};
|
||||
|
||||
use poise::CreateReply;
|
||||
use poise::serenity_prelude::Member;
|
||||
|
||||
/// Subcommands collection for /case command
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
guild_only,
|
||||
subcommands("update"),
|
||||
default_member_permissions = "KICK_MEMBERS | BAN_MEMBERS | MODERATE_MEMBERS"
|
||||
)]
|
||||
pub async fn case(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update a case with new reason
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
guild_only
|
||||
)]
|
||||
pub async fn update(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Case ID to update"] case_id: i32,
|
||||
#[description = "New reason for the case"] reason: String
|
||||
) -> Result<(), Error> {
|
||||
match Moderations::update_case(
|
||||
i64::from(ctx.guild_id().unwrap()),
|
||||
case_id,
|
||||
false,
|
||||
Some(reason.clone())
|
||||
).await {
|
||||
Ok(_) => ctx.send(CreateReply::default().content(format!("Case #{} updated with new reason:\n`{}`", case_id, reason))).await?,
|
||||
Err(e) => ctx.send(CreateReply::default().content(format!("Error updating case ID: {}\nError: {}", case_id, e))).await?
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kick a member
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
guild_only,
|
||||
default_member_permissions = "KICK_MEMBERS",
|
||||
required_bot_permissions = "KICK_MEMBERS"
|
||||
)]
|
||||
pub async fn kick(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Member to be kicked"] member: Member,
|
||||
#[description = "Reason for the kick"] reason: Option<String>
|
||||
) -> Result<(), Error> {
|
||||
let reason = reason.unwrap_or_else(|| "Reason unknown".to_string());
|
||||
let case = Moderations::create_case(
|
||||
i64::from(ctx.guild_id().unwrap()),
|
||||
ActionTypes::Kick,
|
||||
false,
|
||||
i64::from(member.user.id),
|
||||
member.user.tag(),
|
||||
reason.clone(),
|
||||
i64::from(ctx.author().id),
|
||||
ctx.author().tag(),
|
||||
ctx.created_at().timestamp(),
|
||||
None
|
||||
).await?;
|
||||
Moderations::generate_modlog(case.clone(), &ctx.http(), ctx.channel_id().into()).await?;
|
||||
|
||||
member.kick_with_reason(&ctx.http(), &reason).await?;
|
||||
ctx.send(CreateReply::default().content(format!("Member: {}\nReason: `{}`\nType: {}\nModerator: {}", member.user.tag(), reason, capitalize_first(case.action_type.as_str()), ctx.author().tag()))).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Warn a member
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
guild_only,
|
||||
default_member_permissions = "MODERATE_MEMBERS",
|
||||
required_bot_permissions = "MODERATE_MEMBERS"
|
||||
)]
|
||||
pub async fn warn(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Member to be warned"] member: Member,
|
||||
#[description = "Reason for the warn"] reason: Option<String>
|
||||
) -> Result<(), Error> {
|
||||
let reason = reason.unwrap_or_else(|| "Reason unknown".to_string());
|
||||
let case = Moderations::create_case(
|
||||
i64::from(ctx.guild_id().unwrap()),
|
||||
ActionTypes::Warn,
|
||||
false,
|
||||
i64::from(member.user.id),
|
||||
member.user.tag(),
|
||||
reason.clone(),
|
||||
i64::from(ctx.author().id),
|
||||
ctx.author().tag(),
|
||||
ctx.created_at().timestamp(),
|
||||
None
|
||||
).await?;
|
||||
Moderations::generate_modlog(case.clone(), &ctx.http(), ctx.channel_id().into()).await?;
|
||||
|
||||
ctx.send(CreateReply::default().content(format!("Member: {}\nReason: `{}`\nType: {}\nModerator: {}", member.user.tag(), reason, capitalize_first(case.action_type.as_str()), ctx.author().tag()))).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -32,6 +32,33 @@ impl DatabaseController {
|
||||
);
|
||||
").await?;
|
||||
|
||||
// Guild Case IDs
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS guild_case_ids (
|
||||
guild_id BIGINT NOT NULL,
|
||||
max_case_id INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (guild_id)
|
||||
);
|
||||
").await?;
|
||||
|
||||
// ModerationEvents
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS moderation_events (
|
||||
guild_id BIGINT NOT NULL,
|
||||
case_id INT NOT NULL,
|
||||
action_type VARCHAR(255) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
user_id BIGINT NOT NULL,
|
||||
user_tag VARCHAR(255) NOT NULL,
|
||||
reason VARCHAR(1024) NOT NULL,
|
||||
moderator_id BIGINT NOT NULL,
|
||||
moderator_tag VARCHAR(255) NOT NULL,
|
||||
time_created BIGINT NOT NULL,
|
||||
duration BIGINT,
|
||||
PRIMARY KEY (guild_id, case_id)
|
||||
);
|
||||
").await?;
|
||||
|
||||
Ok(DatabaseController { client })
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,14 @@ pub fn concat_message(messages: Vec<String>) -> String {
|
||||
messages.join("\n")
|
||||
}
|
||||
|
||||
pub fn capitalize_first(s: &str) -> String {
|
||||
let mut chars = s.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_duration(secs: u64) -> String {
|
||||
let days = secs / 86400;
|
||||
let hours = (secs % 86400) / 3600;
|
||||
|
12
src/main.rs
12
src/main.rs
@ -47,8 +47,9 @@ async fn on_ready(
|
||||
let commands = Command::set_global_commands(&ctx.http, builder).await;
|
||||
|
||||
match commands {
|
||||
Ok(cmdmap) => for command in cmdmap.iter() {
|
||||
println!("Registered command globally: {}", command.name);
|
||||
Ok(cmdmap) => {
|
||||
let command_box: Vec<_> = cmdmap.iter().map(|cmd| cmd.name.clone()).collect();
|
||||
println!("Registered commands globally: {}", command_box.join("\n- "));
|
||||
},
|
||||
Err(why) => println!("Error registering commands: {:?}", why)
|
||||
}
|
||||
@ -67,7 +68,12 @@ async fn main() {
|
||||
commands::ping::ping(),
|
||||
commands::uptime::uptime(),
|
||||
commands::status::status(),
|
||||
commands::gameserver::gameserver()
|
||||
commands::gameserver::gameserver(),
|
||||
// Separator here to make it easier to read and update moderation stuff below
|
||||
commands::moderation::case(),
|
||||
commands::moderation::update(),
|
||||
commands::moderation::kick(),
|
||||
commands::moderation::warn(),
|
||||
],
|
||||
pre_command: |ctx| Box::pin(async move {
|
||||
let get_guild_name = match ctx.guild() {
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod gameservers;
|
||||
pub mod moderation_events;
|
||||
|
173
src/models/moderation_events.rs
Normal file
173
src/models/moderation_events.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use crate::{
|
||||
controllers::database::DatabaseController,
|
||||
internals::utils::{
|
||||
EMBED_COLOR,
|
||||
capitalize_first
|
||||
}
|
||||
};
|
||||
|
||||
use poise::serenity_prelude::{
|
||||
Http,
|
||||
Error,
|
||||
Timestamp,
|
||||
ChannelId,
|
||||
CreateMessage,
|
||||
CreateEmbed
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Moderations {
|
||||
pub guild_id: i64,
|
||||
pub case_id: i32,
|
||||
pub action_type: ActionTypes,
|
||||
pub is_active: bool,
|
||||
pub user_id: i64,
|
||||
pub user_tag: String,
|
||||
pub reason: String,
|
||||
pub moderator_id: i64,
|
||||
pub moderator_tag: String,
|
||||
pub time_created: i64,
|
||||
pub duration: Option<i64>
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // For temporary suppression until we use all of enums.
|
||||
#[derive(Clone)]
|
||||
pub enum ActionTypes {
|
||||
Ban,
|
||||
Kick,
|
||||
Mute,
|
||||
Warn,
|
||||
Unban,
|
||||
Unmute
|
||||
}
|
||||
|
||||
impl ActionTypes {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
ActionTypes::Ban => "ban",
|
||||
ActionTypes::Kick => "kick",
|
||||
ActionTypes::Mute => "mute",
|
||||
ActionTypes::Warn => "warn",
|
||||
ActionTypes::Unban => "unban",
|
||||
ActionTypes::Unmute => "unmute"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Moderations {
|
||||
pub async fn generate_modlog(case: Moderations, http: &Http, channel_id: u64) -> Result<(), Error> {
|
||||
let time_created_formatted = Timestamp::from_unix_timestamp(case.time_created).expect(" Failed to format timestamp!");
|
||||
let modlog_embed = CreateEmbed::default()
|
||||
.color(EMBED_COLOR)
|
||||
.title(format!("{} • Case #{}", capitalize_first(case.action_type.as_str()), case.case_id))
|
||||
.fields(vec![
|
||||
("User", format!("{}\n<@{}>", case.user_tag, case.user_id), true),
|
||||
("Moderator", format!("{}\n<@{}>", case.moderator_tag, case.moderator_id), true),
|
||||
("\u{200B}", "\u{200B}".to_string(), true),
|
||||
("Reason", format!("`{}`", case.reason), false)
|
||||
])
|
||||
.timestamp(time_created_formatted);
|
||||
|
||||
ChannelId::new(channel_id).send_message(http, CreateMessage::new().embed(modlog_embed)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_case(
|
||||
guild_id: i64,
|
||||
action_type: ActionTypes,
|
||||
is_active: bool,
|
||||
user_id: i64,
|
||||
user_tag: String,
|
||||
reason: String,
|
||||
moderator_id: i64,
|
||||
moderator_tag: String,
|
||||
time_created: i64,
|
||||
duration: Option<i64>
|
||||
) -> Result<Moderations, tokio_postgres::Error> {
|
||||
let _db = DatabaseController::new().await?.client;
|
||||
|
||||
// Get the current max case_id for the guild
|
||||
let stmt = _db.prepare("
|
||||
SELECT max_case_id FROM guild_case_ids WHERE guild_id = $1;
|
||||
").await?;
|
||||
let rows = _db.query(&stmt, &[&guild_id]).await?;
|
||||
let mut max_case_id = if let Some(row) = rows.get(0) {
|
||||
row.get::<_, i32>("max_case_id")
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Increment the max case_id for the guild
|
||||
max_case_id += 1;
|
||||
let stmt = _db.prepare("
|
||||
INSERT INTO guild_case_ids (guild_id, max_case_id) VALUES ($1, $2)
|
||||
ON CONFLICT (guild_id) DO UPDATE SET max_case_id = $2;
|
||||
").await?;
|
||||
_db.execute(&stmt, &[&guild_id, &max_case_id]).await?;
|
||||
|
||||
// Create a new case in database and return the case_id
|
||||
let stmt = _db.prepare("
|
||||
INSERT INTO moderation_events (
|
||||
guild_id, case_id, action_type, is_active, user_id, user_tag, reason, moderator_id, moderator_tag, time_created, duration
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING case_id;
|
||||
").await?;
|
||||
|
||||
let row = _db.query(&stmt, &[
|
||||
&guild_id,
|
||||
&max_case_id,
|
||||
&action_type.as_str(),
|
||||
&is_active,
|
||||
&user_id,
|
||||
&user_tag,
|
||||
&reason,
|
||||
&moderator_id,
|
||||
&moderator_tag,
|
||||
&time_created,
|
||||
&duration
|
||||
]).await?;
|
||||
|
||||
let moderations = Moderations {
|
||||
guild_id,
|
||||
case_id: row[0].get("case_id"),
|
||||
action_type,
|
||||
is_active,
|
||||
user_id,
|
||||
user_tag,
|
||||
reason,
|
||||
moderator_id,
|
||||
moderator_tag,
|
||||
time_created,
|
||||
duration
|
||||
};
|
||||
|
||||
Ok(moderations)
|
||||
}
|
||||
|
||||
pub async fn update_case(
|
||||
guild_id: i64,
|
||||
case_id: i32,
|
||||
is_active: bool,
|
||||
reason: Option<String>
|
||||
) -> Result<(), tokio_postgres::Error> {
|
||||
let _db = DatabaseController::new().await?.client;
|
||||
|
||||
match reason {
|
||||
Some(reason) => {
|
||||
_db.execute("
|
||||
UPDATE moderation_events
|
||||
SET is_active = $3, reason = $4 WHERE guild_id = $1 AND case_id = $2;
|
||||
", &[&guild_id, &case_id, &is_active, &reason]).await?;
|
||||
},
|
||||
None => {
|
||||
_db.execute("
|
||||
UPDATE moderation_events
|
||||
SET is_active = $3 WHERE guild_id = $1 AND case_id = $2;
|
||||
", &[&guild_id, &case_id, &is_active]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user