feat(ticket): cooldown 5m after close (DB ticket_cooldowns + check on create)

This commit is contained in:
loki5512344 2026-09-21 21:40:29 +02:00
parent 9c0b2624e2
commit 2a7ace8c09
Signed by: boba
GPG key ID: 253067914055423B
4 changed files with 75 additions and 2 deletions

View file

@ -12,8 +12,12 @@ pub async fn run(pool: &SqlitePool) -> anyhow::Result<()> {
.execute(pool) .execute(pool)
.await?; .await?;
let migrations = let migrations = [
[("V1__initial", V1_INITIAL), ("V2__tickets", V2_TICKETS), ("V3__reminders", V3_REMINDERS)]; ("V1__initial", V1_INITIAL),
("V2__tickets", V2_TICKETS),
("V3__reminders", V3_REMINDERS),
("V4__ticket_cooldown", V4_COOLDOWN),
];
for (name, sql) in migrations { for (name, sql) in migrations {
let exists: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM _migrations WHERE name = ?") let exists: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM _migrations WHERE name = ?")
.bind(name) .bind(name)
@ -76,3 +80,11 @@ CREATE TABLE IF NOT EXISTS reminders (
sent INTEGER NOT NULL DEFAULT 0 sent INTEGER NOT NULL DEFAULT 0
); );
"; ";
const V4_COOLDOWN: &str = "
CREATE TABLE IF NOT EXISTS ticket_cooldowns (
user_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
expires_at INTEGER NOT NULL,
PRIMARY KEY (user_id, guild_id)
);
";

View file

@ -146,8 +146,18 @@ pub async fn handle_close_modal(ctx: Context, interaction: serenity::all::ModalI
haste_url.clone(), haste_url.clone(),
) )
.await; .await;
let opener_for_cooldown = if let Some(pool) = service::pool() {
service::get_opener(pool, channel_id.get()).await
} else {
None
};
if let Some(pool) = service::pool() { if let Some(pool) = service::pool() {
service::cleanup_ticket(pool, channel_id.get()).await; service::cleanup_ticket(pool, channel_id.get()).await;
if let Some(opener) = opener_for_cooldown {
if let Some(gid) = interaction.guild_id {
service::set_cooldown(pool, gid.get(), opener, 300).await;
}
}
} }
let control_row = serenity::all::CreateActionRow::Buttons(vec![ let control_row = serenity::all::CreateActionRow::Buttons(vec![
serenity::all::CreateButton::new("ticket_reopen") serenity::all::CreateButton::new("ticket_reopen")

View file

@ -136,6 +136,20 @@ pub async fn on_modal(ctx: Context, interaction: ModalInteraction) {
.await; .await;
return; return;
} }
if let Some(rem) = service::is_on_cooldown(pool, guild_id.get(), user.id.get()).await {
let _ = interaction
.create_followup(
&ctx.http,
serenity::all::CreateInteractionResponseFollowup::new()
.content(format!(
"Cooldown: wait {}s before new ticket (5m after close).",
rem
))
.ephemeral(true),
)
.await;
return;
}
} }
if let Ok(channels) = guild_id.channels(&ctx.http).await { if let Ok(channels) = guild_id.channels(&ctx.http).await {
let exists = channels.values().any(|c| { let exists = channels.values().any(|c| {

View file

@ -65,6 +65,43 @@ pub async fn cleanup_ticket(p: &SqlitePool, c: u64) {
let _ = sqlx::query("DELETE FROM tickets WHERE channel_id=?").bind(c.to_string()).execute(p).await; let _ = sqlx::query("DELETE FROM tickets WHERE channel_id=?").bind(c.to_string()).execute(p).await;
let _ = sqlx::query("DELETE FROM ticket_claims WHERE channel_id=?").bind(c.to_string()).execute(p).await; let _ = sqlx::query("DELETE FROM ticket_claims WHERE channel_id=?").bind(c.to_string()).execute(p).await;
} }
pub async fn is_on_cooldown(p: &SqlitePool, g: u64, u: u64) -> Option<u64> {
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS ticket_cooldowns (user_id TEXT NOT NULL, guild_id TEXT NOT NULL, expires_at INTEGER NOT NULL, PRIMARY KEY (user_id, guild_id))").execute(p).await;
let row: Option<(i64,)> =
sqlx::query_as("SELECT expires_at FROM ticket_cooldowns WHERE user_id=? AND guild_id=?")
.bind(u.to_string())
.bind(g.to_string())
.fetch_optional(p)
.await
.unwrap_or(None);
if let Some((exp,)) = row {
let now =
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()
as i64;
if exp > now {
return Some((exp - now) as u64);
}
let _ = sqlx::query("DELETE FROM ticket_cooldowns WHERE user_id=? AND guild_id=?")
.bind(u.to_string())
.bind(g.to_string())
.execute(p)
.await;
}
None
}
pub async fn set_cooldown(p: &SqlitePool, g: u64, u: u64, secs: u64) {
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS ticket_cooldowns (user_id TEXT NOT NULL, guild_id TEXT NOT NULL, expires_at INTEGER NOT NULL, PRIMARY KEY (user_id, guild_id))").execute(p).await;
let exp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs()
+ secs;
let _ = sqlx::query(
"INSERT OR REPLACE INTO ticket_cooldowns(user_id, guild_id, expires_at) VALUES(?,?,?)",
)
.bind(u.to_string())
.bind(g.to_string())
.bind(exp as i64)
.execute(p)
.await;
}
pub async fn add_user_to_ticket( pub async fn add_user_to_ticket(
http: &Http, http: &Http,
channel_id: ChannelId, channel_id: ChannelId,