add invite commands

This commit is contained in:
Puechberty Arthur
2026-04-10 19:48:21 +02:00
parent 65420838fb
commit 9ea4914e42
9 changed files with 686 additions and 22 deletions
+108
View File
@@ -0,0 +1,108 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, add_invite_count};
pub async fn handle_addinvite(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.is_empty() || args.len() > 2 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Usage: `+addinvite [@user/id] <nombre>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let (target_user, amount_arg) = if args.len() == 1 {
(msg.author.id, args[0])
} else {
let Some(user_id) = parse_user_id(args[0]) else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Utilisateur invalide. Utilise une mention ou un ID.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
(user_id, args[1])
};
let Ok(amount) = amount_arg.parse::<i64>() else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Le nombre doit etre un entier positif.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
if amount <= 0 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Le nombre doit etre superieur a 0.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}) else {
return;
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_raw = guild_id.get() as i64;
let user_id_raw = target_user.get() as i64;
let Ok(new_total) = add_invite_count(&pool, bot_id, guild_id_raw, user_id_raw, amount).await
else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de mettre a jour les invitations.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("AddInvite")
.description(format!(
"{} invitation(s) ajoutee(s) a <@{}>.\nNouveau total: `{}`.",
amount,
target_user.get(),
new_total
))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct AddInviteCommand;
pub static COMMAND_DESCRIPTOR: AddInviteCommand = AddInviteCommand;
impl crate::commands::command_contract::CommandSpec for AddInviteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
name: "addinvite",
category: "invitation",
params: "[@membre/ID] <nombre>",
description: "Ajoute un nombre specifique d invitations a un utilisateur.",
examples: &[
"+addinvite @User 3",
"+addinvite 123456789012345678 2",
"+help addinvite",
],
default_aliases: &["ainv"],
allow_in_dm: false,
default_permission: 5,
}
}
}
+85
View File
@@ -0,0 +1,85 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, get_invite_count};
pub async fn handle_invite(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.len() > 1 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Usage: `+invite [@user/id]`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let target_user = if args.is_empty() {
msg.author.id
} else {
let Some(user_id) = parse_user_id(args[0]) else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Utilisateur invalide. Utilise une mention ou un ID.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
user_id
};
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}) else {
return;
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_raw = guild_id.get() as i64;
let user_id_raw = target_user.get() as i64;
let Ok(invite_count) = get_invite_count(&pool, bot_id, guild_id_raw, user_id_raw).await else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de lire le compteur d invitations.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("Invite")
.description(format!(
"<@{}> a actuellement `{}` invitation(s).",
target_user.get(),
invite_count
))
.color(0x5865F2);
send_embed(ctx, msg, embed).await;
}
pub struct InviteCommand;
pub static COMMAND_DESCRIPTOR: InviteCommand = InviteCommand;
impl crate::commands::command_contract::CommandSpec for InviteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
name: "invite",
category: "invitation",
params: "[@membre/ID]",
description: "Affiche le nombre d invitations d un utilisateur.",
examples: &["+invite", "+invite @User", "+help invite"],
default_aliases: &["inv"],
allow_in_dm: false,
default_permission: 0,
}
}
}
+73
View File
@@ -0,0 +1,73 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, list_invite_board};
pub async fn handle_inviteboard(ctx: &Context, msg: &Message, _args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}) else {
return;
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_raw = guild_id.get() as i64;
let Ok(entries) = list_invite_board(&pool, bot_id, guild_id_raw, 10).await else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de lire le classement des invitations.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
if entries.is_empty() {
let embed = CreateEmbed::new()
.title("InviteBoard")
.description("Aucune invitation enregistree pour le moment.")
.color(0x5865F2);
send_embed(ctx, msg, embed).await;
return;
}
let lines = entries
.iter()
.enumerate()
.map(|(idx, (user_id, count))| {
format!("`#{}` <@{}> - `{}` invitation(s)", idx + 1, user_id, count)
})
.collect::<Vec<_>>()
.join("\n");
let embed = CreateEmbed::new()
.title("InviteBoard")
.description(lines)
.color(0x5865F2);
send_embed(ctx, msg, embed).await;
}
pub struct InviteBoardCommand;
pub static COMMAND_DESCRIPTOR: InviteBoardCommand = InviteBoardCommand;
impl crate::commands::command_contract::CommandSpec for InviteBoardCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
name: "inviteboard",
category: "invitation",
params: "aucun",
description: "Affiche les 10 membres du serveur avec le plus d invitations.",
examples: &["+inviteboard", "+iboard", "+help inviteboard"],
default_aliases: &["iboard"],
allow_in_dm: false,
default_permission: 0,
}
}
}
+105
View File
@@ -0,0 +1,105 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, reset_invite_count_for_user, reset_invite_counts_for_guild};
pub async fn handle_invitereset(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.len() != 1 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Usage: `+invitereset <@user|guild>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}) else {
return;
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_raw = guild_id.get() as i64;
let target = args[0].to_lowercase();
if matches!(target.as_str(), "guild" | "serveur" | "server") {
let Ok(affected) = reset_invite_counts_for_guild(&pool, bot_id, guild_id_raw).await else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de reinitialiser les invitations du serveur.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("InviteReset")
.description(format!(
"Compteurs d invitations du serveur reinitialises. Entrees supprimees: `{}`.",
affected
))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
return;
}
let Some(user_id) = parse_user_id(args[0]) else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Cible invalide. Utilise une mention utilisateur, un ID, ou `guild`.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let Ok(_) =
reset_invite_count_for_user(&pool, bot_id, guild_id_raw, user_id.get() as i64).await
else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de reinitialiser les invitations de cet utilisateur.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("InviteReset")
.description(format!(
"Compteur d invitations reinitialise pour <@{}>.",
user_id.get()
))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct InviteResetCommand;
pub static COMMAND_DESCRIPTOR: InviteResetCommand = InviteResetCommand;
impl crate::commands::command_contract::CommandSpec for InviteResetCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
name: "invitereset",
category: "invitation",
params: "<@membre/ID|guild>",
description: "Reinitialise le compteur d invitations pour un utilisateur ou le serveur.",
examples: &[
"+invitereset @User",
"+invitereset guild",
"+help invitereset",
],
default_aliases: &["invreset"],
allow_in_dm: false,
default_permission: 5,
}
}
}
+108
View File
@@ -0,0 +1,108 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, add_invite_count};
pub async fn handle_removeinvite(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.is_empty() || args.len() > 2 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Usage: `+removeinvite [@user/id] <nombre>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let (target_user, amount_arg) = if args.len() == 1 {
(msg.author.id, args[0])
} else {
let Some(user_id) = parse_user_id(args[0]) else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Utilisateur invalide. Utilise une mention ou un ID.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
(user_id, args[1])
};
let Ok(amount) = amount_arg.parse::<i64>() else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Le nombre doit etre un entier positif.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
if amount <= 0 {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Le nombre doit etre superieur a 0.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}) else {
return;
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_raw = guild_id.get() as i64;
let user_id_raw = target_user.get() as i64;
let Ok(new_total) = add_invite_count(&pool, bot_id, guild_id_raw, user_id_raw, -amount).await
else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de mettre a jour les invitations.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("RemoveInvite")
.description(format!(
"{} invitation(s) retiree(s) a <@{}>.\nNouveau total: `{}`.",
amount,
target_user.get(),
new_total
))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct RemoveInviteCommand;
pub static COMMAND_DESCRIPTOR: RemoveInviteCommand = RemoveInviteCommand;
impl crate::commands::command_contract::CommandSpec for RemoveInviteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
name: "removeinvite",
category: "invitation",
params: "[@membre/ID] <nombre>",
description: "Retire un nombre specifique d invitations a un utilisateur.",
examples: &[
"+removeinvite @User 1",
"+removeinvite 123456789012345678 2",
"+help removeinvite",
],
default_aliases: &["rinv"],
allow_in_dm: false,
default_permission: 5,
}
}
}
+14 -2
View File
@@ -1,5 +1,7 @@
use crate::commands::command_contract::{CommandMetadata, CommandSpec}; use crate::commands::command_contract::{CommandMetadata, CommandSpec};
#[path = "invitation/addinvite.rs"]
pub mod addinvite;
#[path = "roles/addrole.rs"] #[path = "roles/addrole.rs"]
pub mod addrole; pub mod addrole;
#[path = "../utils/admin_common.rs"] #[path = "../utils/admin_common.rs"]
@@ -150,8 +152,12 @@ pub mod hideall;
pub mod idle; pub mod idle;
#[path = "botconfig/invisible.rs"] #[path = "botconfig/invisible.rs"]
pub mod invisible; pub mod invisible;
#[path = "owner/invite.rs"] #[path = "invitation/invite.rs"]
pub mod invite; pub mod invite;
#[path = "invitation/inviteboard.rs"]
pub mod inviteboard;
#[path = "invitation/invitereset.rs"]
pub mod invitereset;
#[path = "config/join.rs"] #[path = "config/join.rs"]
pub mod join; pub mod join;
#[path = "mod/kick.rs"] #[path = "mod/kick.rs"]
@@ -252,6 +258,8 @@ pub mod punishsetup;
pub mod raidlog; pub mod raidlog;
#[path = "botconfig/removeactivity.rs"] #[path = "botconfig/removeactivity.rs"]
pub mod remove_activity; pub mod remove_activity;
#[path = "invitation/removeinvite.rs"]
pub mod removeinvite;
#[path = "ticket/rename.rs"] #[path = "ticket/rename.rs"]
pub mod rename; pub mod rename;
#[path = "channel/renew.rs"] #[path = "channel/renew.rs"]
@@ -390,6 +398,11 @@ pub mod watch;
pub fn all_command_metadata() -> Vec<CommandMetadata> { pub fn all_command_metadata() -> Vec<CommandMetadata> {
vec![ vec![
ping::COMMAND_DESCRIPTOR.metadata(), ping::COMMAND_DESCRIPTOR.metadata(),
addinvite::COMMAND_DESCRIPTOR.metadata(),
invite::COMMAND_DESCRIPTOR.metadata(),
inviteboard::COMMAND_DESCRIPTOR.metadata(),
invitereset::COMMAND_DESCRIPTOR.metadata(),
removeinvite::COMMAND_DESCRIPTOR.metadata(),
timeout::COMMAND_DESCRIPTOR.metadata(), timeout::COMMAND_DESCRIPTOR.metadata(),
allbots::COMMAND_DESCRIPTOR.metadata(), allbots::COMMAND_DESCRIPTOR.metadata(),
alladmins::COMMAND_DESCRIPTOR.metadata(), alladmins::COMMAND_DESCRIPTOR.metadata(),
@@ -559,7 +572,6 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
mpsettings::COMMAND_DESCRIPTOR.metadata(), mpsettings::COMMAND_DESCRIPTOR.metadata(),
mpsent::COMMAND_DESCRIPTOR.metadata(), mpsent::COMMAND_DESCRIPTOR.metadata(),
mpdelete::COMMAND_DESCRIPTOR.metadata(), mpdelete::COMMAND_DESCRIPTOR.metadata(),
invite::COMMAND_DESCRIPTOR.metadata(),
leave::COMMAND_DESCRIPTOR.metadata(), leave::COMMAND_DESCRIPTOR.metadata(),
leave_settings::COMMAND_DESCRIPTOR.metadata(), leave_settings::COMMAND_DESCRIPTOR.metadata(),
viewlogs::COMMAND_DESCRIPTOR.metadata(), viewlogs::COMMAND_DESCRIPTOR.metadata(),
+19 -1
View File
@@ -86,6 +86,11 @@ const HELP_PAGES: &[HelpPage] = &[
title: "Infos", title: "Infos",
description: "Informations sur le serveur, les membres et les profils.", description: "Informations sur le serveur, les membres et les profils.",
}, },
HelpPage {
key: "invitation",
title: "Invitation",
description: "Compteurs d invitations et classement des invitations du serveur.",
},
HelpPage { HelpPage {
key: "logs", key: "logs",
title: "Logs", title: "Logs",
@@ -132,6 +137,7 @@ fn help_page_for_command(
meta: &crate::commands::command_contract::CommandMetadata, meta: &crate::commands::command_contract::CommandMetadata,
) -> &'static str { ) -> &'static str {
match meta.name { match meta.name {
"addinvite" | "removeinvite" | "invite" | "invitereset" | "inviteboard" => "invitation",
"modlog" | "messagelog" | "voicelog" | "boostlog" | "rolelog" | "raidlog" "modlog" | "messagelog" | "voicelog" | "boostlog" | "rolelog" | "raidlog"
| "autoconfiglog" | "nolog" | "joinsettings" | "boostembed" | "setmodlogs" | "autoconfiglog" | "nolog" | "joinsettings" | "boostembed" | "setmodlogs"
| "setboostembed" | "leavesettings" | "viewlogs" => "logs", | "setboostembed" | "leavesettings" | "viewlogs" => "logs",
@@ -151,12 +157,13 @@ fn help_page_for_command(
| "idle" | "dnd" | "invisible" | "change" | "changereset" | "changeall" => "bot", | "idle" | "dnd" | "invisible" | "change" | "changereset" | "changeall" => "bot",
"owner" | "unowner" | "clearowners" | "bl" | "unbl" | "blinfo" | "clearbl" | "allbots" "owner" | "unowner" | "clearowners" | "bl" | "unbl" | "blinfo" | "clearbl" | "allbots"
| "alladmins" | "botadmins" | "mainprefix" | "prefix" | "mp" | "mpsettings" | "mpsent" | "alladmins" | "botadmins" | "mainprefix" | "prefix" | "mp" | "mpsettings" | "mpsent"
| "mpdelete" | "invite" | "leave" | "discussion" => "administration", | "mpdelete" | "leave" | "discussion" => "administration",
"perms" | "delperm" | "clearperms" | "allperms" | "alias" | "help" | "helpsetting" => { "perms" | "delperm" | "clearperms" | "allperms" | "alias" | "help" | "helpsetting" => {
"permissions" "permissions"
} }
_ => match meta.category { _ => match meta.category {
"info" => "infos", "info" => "infos",
"invitation" => "invitation",
"mod" => "moderation", "mod" => "moderation",
"config" => "logs", "config" => "logs",
"botconfig" => "bot", "botconfig" => "bot",
@@ -219,6 +226,13 @@ fn help_page_matches_input(page: &HelpPage, input: &str) -> bool {
let normalized = help_lookup_key(input); let normalized = help_lookup_key(input);
let aliases = match page.key { let aliases = match page.key {
"infos" => &["general", "info", "informations"][..], "infos" => &["general", "info", "informations"][..],
"invitation" => &[
"invite",
"invites",
"invitation",
"invitations",
"inviteboard",
][..],
"logs" => &["log", "journal"][..], "logs" => &["log", "journal"][..],
"moderation" => &["mod", "sanction"][..], "moderation" => &["mod", "sanction"][..],
"roles" => &["role", "roles"][..], "roles" => &["role", "roles"][..],
@@ -424,6 +438,10 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
"mpsettings" => Some("mpsettings"), "mpsettings" => Some("mpsettings"),
"mpsent" => Some("mpsent"), "mpsent" => Some("mpsent"),
"mpdelete" | "mpdel" => Some("mpdelete"), "mpdelete" | "mpdel" => Some("mpdelete"),
"add invite" | "addinvite" => Some("addinvite"),
"remove invite" | "removeinvite" => Some("removeinvite"),
"invite reset" | "invitereset" => Some("invitereset"),
"invite board" | "inviteboard" => Some("inviteboard"),
"discussion" => Some("discussion"), "discussion" => Some("discussion"),
"owner" => Some("owner"), "owner" => Some("owner"),
"unowner" => Some("unowner"), "unowner" => Some("unowner"),
+151
View File
@@ -184,6 +184,16 @@ pub struct TempvocProfile {
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }
#[derive(Debug, Clone, sqlx::FromRow)]
#[allow(dead_code)]
pub struct InviteCounter {
pub bot_id: i64,
pub guild_id: i64,
pub user_id: i64,
pub invite_count: i64,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, sqlx::FromRow)] #[derive(Debug, Clone, sqlx::FromRow)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct ModerationSettings { pub struct ModerationSettings {
@@ -906,6 +916,30 @@ pub async fn init_schema(pool: &PgPool) -> Result<(), sqlx::Error> {
.execute(pool) .execute(pool)
.await?; .await?;
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS bot_invite_counts (
bot_id BIGINT NOT NULL,
guild_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
invite_count BIGINT NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (bot_id, guild_id, user_id)
);
"#,
)
.execute(pool)
.await?;
sqlx::query(
r#"
CREATE INDEX IF NOT EXISTS idx_bot_invite_counts_lookup
ON bot_invite_counts (bot_id, guild_id, invite_count DESC, updated_at ASC);
"#,
)
.execute(pool)
.await?;
sqlx::query( sqlx::query(
r#" r#"
CREATE TABLE IF NOT EXISTS bot_tempvoc_settings ( CREATE TABLE IF NOT EXISTS bot_tempvoc_settings (
@@ -3240,6 +3274,123 @@ pub async fn is_piconly_channel(
Ok(row.0) Ok(row.0)
} }
// ========== INVITE COUNTERS FUNCTIONS ==========
pub async fn get_invite_count(
pool: &PgPool,
bot_id: i64,
guild_id: i64,
user_id: i64,
) -> Result<i64, sqlx::Error> {
let row = sqlx::query_as::<_, (i64,)>(
r#"
SELECT invite_count
FROM bot_invite_counts
WHERE bot_id = $1 AND guild_id = $2 AND user_id = $3;
"#,
)
.bind(bot_id)
.bind(guild_id)
.bind(user_id)
.fetch_optional(pool)
.await?;
Ok(row.map(|value| value.0).unwrap_or(0))
}
pub async fn add_invite_count(
pool: &PgPool,
bot_id: i64,
guild_id: i64,
user_id: i64,
delta: i64,
) -> Result<i64, sqlx::Error> {
let row = sqlx::query_as::<_, (i64,)>(
r#"
INSERT INTO bot_invite_counts (bot_id, guild_id, user_id, invite_count)
VALUES ($1, $2, $3, GREATEST($4, 0))
ON CONFLICT (bot_id, guild_id, user_id)
DO UPDATE SET
invite_count = GREATEST(bot_invite_counts.invite_count + $4, 0),
updated_at = NOW()
RETURNING invite_count;
"#,
)
.bind(bot_id)
.bind(guild_id)
.bind(user_id)
.bind(delta)
.fetch_one(pool)
.await?;
Ok(row.0)
}
pub async fn reset_invite_count_for_user(
pool: &PgPool,
bot_id: i64,
guild_id: i64,
user_id: i64,
) -> Result<u64, sqlx::Error> {
let result = sqlx::query(
r#"
DELETE FROM bot_invite_counts
WHERE bot_id = $1 AND guild_id = $2 AND user_id = $3;
"#,
)
.bind(bot_id)
.bind(guild_id)
.bind(user_id)
.execute(pool)
.await?;
Ok(result.rows_affected())
}
pub async fn reset_invite_counts_for_guild(
pool: &PgPool,
bot_id: i64,
guild_id: i64,
) -> Result<u64, sqlx::Error> {
let result = sqlx::query(
r#"
DELETE FROM bot_invite_counts
WHERE bot_id = $1 AND guild_id = $2;
"#,
)
.bind(bot_id)
.bind(guild_id)
.execute(pool)
.await?;
Ok(result.rows_affected())
}
pub async fn list_invite_board(
pool: &PgPool,
bot_id: i64,
guild_id: i64,
limit: i64,
) -> Result<Vec<(i64, i64)>, sqlx::Error> {
let capped_limit = limit.clamp(1, 100);
let rows = sqlx::query_as::<_, (i64, i64)>(
r#"
SELECT user_id, invite_count
FROM bot_invite_counts
WHERE bot_id = $1 AND guild_id = $2 AND invite_count > 0
ORDER BY invite_count DESC, updated_at ASC
LIMIT $3;
"#,
)
.bind(bot_id)
.bind(guild_id)
.bind(capped_limit)
.fetch_all(pool)
.await?;
Ok(rows)
}
// ========== ANCIEN SETTINGS FUNCTIONS ========== // ========== ANCIEN SETTINGS FUNCTIONS ==========
pub async fn get_or_create_old_member_settings( pub async fn get_or_create_old_member_settings(
+23 -19
View File
@@ -6,25 +6,25 @@ use std::sync::{Mutex, OnceLock};
use crate::commands::moderation_tools; use crate::commands::moderation_tools;
use crate::commands::remove_activity; use crate::commands::remove_activity;
use crate::commands::{ use crate::commands::{
addrole, alias, ancien, antilink, antimassmention, antiraideautoconfig, antispam, autobackup, addinvite, addrole, alias, ancien, antilink, antimassmention, antiraideautoconfig, antispam,
autoconfiglog, autopublish, autopublishoff, autopublishon, autoreact, backup, badwords, ban, autobackup, autoconfiglog, autopublish, autopublishoff, autopublishon, autoreact, backup,
banlist, banner, bl, blinfo, boostembed, boosters, boostlog, bringall, button, calc, change, badwords, ban, banlist, banner, bl, blinfo, boostembed, boosters, boostlog, bringall, button,
changeall, changereset, channel, choose, claim, cleanup, clear_all_sanctions, clear_badwords, calc, change, changeall, changereset, channel, choose, claim, cleanup, clear_all_sanctions,
clear_bl, clear_limit, clear_messages, clear_owners, clear_perms, clear_sanctions, close, clear_badwords, clear_bl, clear_limit, clear_messages, clear_owners, clear_perms,
cmute, compet, create, del_sanction, delperm, delrole, derank, discussion, dnd, embed, emoji, clear_sanctions, close, cmute, compet, create, del_sanction, delperm, delrole, derank,
end, endgiveaway, giveaway, help, helpsetting, hide, hideall, idle, invisible, invite, join, discussion, dnd, embed, emoji, end, endgiveaway, giveaway, help, helpsetting, hide, hideall,
kick, leave, leave_settings, link, listen, loading, lock, lockall, mainprefix, massiverole, idle, invisible, invite, inviteboard, invitereset, join, kick, leave, leave_settings, link,
member, messagelog, modlog, mp, mpdelete, mpsent, mpsettings, mute, mutelist, muterole, listen, loading, lock, lockall, mainprefix, massiverole, member, messagelog, modlog, mp,
newsticker, noderank, noderankadd, noderankdel, nolog, online, owner, perms, pic, piconly, mpdelete, mpsent, mpsettings, mute, mutelist, muterole, newsticker, noderank, noderankadd,
piconlyadd, piconlydel, ping, playto, prefix, public, punish, punishadd, punishdel, noderankdel, nolog, online, owner, perms, pic, piconly, piconlyadd, piconlydel, ping, playto,
punishsetup, raidlog, rename, renew, reroll, resetantiraide, role, rolelog, rolemembers, prefix, public, punish, punishadd, punishdel, punishsetup, raidlog, removeinvite, rename,
rolemenu, sanctions, say, serverbanner, serverinfo, serverlist, serverpic, set_boostembed, renew, reroll, resetantiraide, role, rolelog, rolemembers, rolemenu, sanctions, say,
set_modlogs, set_muterole, setbanner, setname, setperm, setpic, setprofil, shadowbot, showpics, serverbanner, serverinfo, serverlist, serverpic, set_boostembed, set_modlogs, set_muterole,
slowmode, snipe, spam, stream, strikes, suggestion, suggestionsettings, sync, tempban, setbanner, setname, setperm, setpic, setprofil, shadowbot, showpics, slowmode, snipe, spam,
tempcmute, tempmute, temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member, tickets, stream, strikes, suggestion, suggestionsettings, sync, tempban, tempcmute, tempmute, temprole,
timeout, unalias, unban, unbanall, unbl, uncmute, unhide, unhideall, unlock, unlockall, tempvoc, tempvoc_cmd, theme, ticket, ticket_member, tickets, timeout, unalias, unban, unbanall,
unmassiverole, unmute, unmuteall, unowner, untemprole, user, viewlogs, vocinfo, voicekick, unbl, uncmute, unhide, unhideall, unlock, unlockall, unmassiverole, unmute, unmuteall, unowner,
voicelog, voicemove, warn, watch, untemprole, user, viewlogs, vocinfo, voicekick, voicelog, voicemove, warn, watch,
}; };
use crate::commands::{alladmins, allbots, allperms, botadmins}; use crate::commands::{alladmins, allbots, allperms, botadmins};
use crate::db::{DbPoolKey, upsert_message_observed}; use crate::db::{DbPoolKey, upsert_message_observed};
@@ -418,7 +418,11 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
"mpsent" => mpsent::handle_mpsent_command(ctx, msg, &args).await, "mpsent" => mpsent::handle_mpsent_command(ctx, msg, &args).await,
"mpdelete" | "mpdel" => mpdelete::handle_mpdelete_command(ctx, msg, &args).await, "mpdelete" | "mpdel" => mpdelete::handle_mpdelete_command(ctx, msg, &args).await,
"mp" => mp::handle_mp(ctx, msg, &args).await, "mp" => mp::handle_mp(ctx, msg, &args).await,
"addinvite" => addinvite::handle_addinvite(ctx, msg, &args).await,
"invite" => invite::handle_invite(ctx, msg, &args).await, "invite" => invite::handle_invite(ctx, msg, &args).await,
"removeinvite" => removeinvite::handle_removeinvite(ctx, msg, &args).await,
"invitereset" => invitereset::handle_invitereset(ctx, msg, &args).await,
"inviteboard" => inviteboard::handle_inviteboard(ctx, msg, &args).await,
"leavesettings" => leave_settings::handle_leave_settings(ctx, msg, &args).await, "leavesettings" => leave_settings::handle_leave_settings(ctx, msg, &args).await,
"leave" => leave::handle_leave(ctx, msg, &args).await, "leave" => leave::handle_leave(ctx, msg, &args).await,
"viewlogs" => viewlogs::handle_viewlogs(ctx, msg, &args).await, "viewlogs" => viewlogs::handle_viewlogs(ctx, msg, &args).await,