From 672a508d9df1f7ec2593341e8bf2a227d66a8c23 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Sun, 14 Sep 2025 08:00:51 -0400 Subject: [PATCH] Change private help ticket cooldown --- src/buttons/claim-ticket.ts | 3 +-- src/buttons/close-ticket.ts | 4 +++- src/buttons/dev-watch.ts | 1 - src/buttons/private-help.ts | 8 +++---- src/buttons/unclaim-ticket.ts | 3 +-- src/index.ts | 4 ---- src/modals/add-note-modal.ts | 1 - src/modals/open-ticket-modal.ts | 9 +++++--- src/modals/sync-name-modal.ts | 1 - src/shared/Database.ts | 39 +++++++++++++++++++++++++++++---- src/shared/ticket-cooldown.ts | 9 -------- src/types/database-types.d.ts | 7 ++++++ src/types/index.d.ts | 10 ++++----- src/utils/discord-user-utils.ts | 9 ++++++++ 14 files changed, 70 insertions(+), 38 deletions(-) delete mode 100644 src/shared/ticket-cooldown.ts diff --git a/src/buttons/claim-ticket.ts b/src/buttons/claim-ticket.ts index f006f6b..0cc52c8 100644 --- a/src/buttons/claim-ticket.ts +++ b/src/buttons/claim-ticket.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle, MessageFlags, ChannelType, PermissionFlagsBits, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; export default { @@ -19,7 +18,7 @@ export default { const guildSettings = await Database.getGuildSettings(guild.id); if (!guildSettings || !guildSettings.private_help_role_id) - return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to create ticket.' }); + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to claim ticket.' }); const closeButton = new ButtonBuilder() .setCustomId('close-ticket') diff --git a/src/buttons/close-ticket.ts b/src/buttons/close-ticket.ts index dbcd210..1578be4 100644 --- a/src/buttons/close-ticket.ts +++ b/src/buttons/close-ticket.ts @@ -1,5 +1,5 @@ import { ActionRowBuilder, ButtonInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle, MessageFlags, ChannelType, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; +import { Database } from '../shared/Database'; export default { name: 'close-ticket', @@ -14,6 +14,8 @@ export default { components: [] }); + await Database.closePrivateHelpTicket(channel.id); + await channel.send(`This ticket has been closed by ${interaction.user}`); await interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Ticket closed.' }); diff --git a/src/buttons/dev-watch.ts b/src/buttons/dev-watch.ts index c89cc40..05bffcc 100644 --- a/src/buttons/dev-watch.ts +++ b/src/buttons/dev-watch.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle, MessageFlags } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; export default { diff --git a/src/buttons/private-help.ts b/src/buttons/private-help.ts index f81c17e..fb94585 100644 --- a/src/buttons/private-help.ts +++ b/src/buttons/private-help.ts @@ -1,13 +1,11 @@ import { ActionRowBuilder, ButtonInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle, MessageFlags } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; +import { canOpenPrivateHelpTicket } from '../utils'; export default { name: 'private-help', handler: async function (client: Client, interaction: ButtonInteraction) { - const allowedAt = ticketCooldownMap.get(interaction.user.id); - - if (allowedAt && Date.now() < allowedAt) - return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'You can only create one ticket per day.' }); + if (!(await canOpenPrivateHelpTicket(interaction.user.id))) + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'You can only have one open ticket at a time that is less than a day old.' }); const modal = new ModalBuilder() .setCustomId('open-ticket-modal') diff --git a/src/buttons/unclaim-ticket.ts b/src/buttons/unclaim-ticket.ts index 29f887f..f6f1a9d 100644 --- a/src/buttons/unclaim-ticket.ts +++ b/src/buttons/unclaim-ticket.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle, MessageFlags, ChannelType, PermissionFlagsBits, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; export default { @@ -21,7 +20,7 @@ export default { const guildSettings = await Database.getGuildSettings(guild.id); if (!guildSettings || !guildSettings.private_help_role_id) - return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to create ticket.' }); + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to unclaim ticket.' }); const closeButton = new ButtonBuilder() .setCustomId('close-ticket') diff --git a/src/index.ts b/src/index.ts index 3aba22a..e8e82b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,6 @@ import { checkExpiredBans, initIfNecessary, loadHandlersFrom, refreshCommands } import { initializeWebserver } from './webserver'; import { Database } from './shared/Database'; import { handleAuditLogCreate, handleBanRemove, handleBulkMessageDelete, handleGuildCreate, handleMemberJoin, handleMessageCreate, handleMessageDelete, handleMessageUpdate, handleThreadCreate, handleVoiceStateUpdate } from './events'; -import { pruneOldTickets, ticketCooldownMap } from './shared/ticket-cooldown'; import { openRedisClient } from './shared/RedisClient'; let ready = false; @@ -137,9 +136,6 @@ client.on('ready', async () => { await initializeWebserver(client); - // Prune ticket cooldowns that are expired every day - setInterval(pruneOldTickets, 8.64e+7); - // Check for expired bans every 5 minutes checkExpiredBans(client); setInterval(checkExpiredBans.bind(null, client), 300000); diff --git a/src/modals/add-note-modal.ts b/src/modals/add-note-modal.ts index fcb296f..9c90edf 100644 --- a/src/modals/add-note-modal.ts +++ b/src/modals/add-note-modal.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, GuildTextBasedChannel, MessageFlags, ModalSubmitInteraction, TextChannel, ThreadAutoArchiveDuration } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; import { logCustomEvent } from '../utils'; diff --git a/src/modals/open-ticket-modal.ts b/src/modals/open-ticket-modal.ts index df177a7..07e82e2 100644 --- a/src/modals/open-ticket-modal.ts +++ b/src/modals/open-ticket-modal.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, GuildTextBasedChannel, MessageFlags, ModalSubmitInteraction, TextChannel, ThreadAutoArchiveDuration } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; export default { @@ -13,8 +12,6 @@ export default { if (!guildSettings || !guildSettings.private_help_role_id) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to create ticket. Please report this to a staff member.' }); - ticketCooldownMap.set(interaction.user.id, Date.now() + 8.64e+7); - const reason = interaction.fields.getTextInputValue('ticket-message'); const channel = (await interaction.channel?.fetch()) as TextChannel; @@ -26,6 +23,12 @@ export default { type: ChannelType.PrivateThread }); + console.log(1); + + await Database.createPrivateHelpTicket(interaction.user.id, thread.id); + + console.log(2); + const closeButton = new ButtonBuilder() .setCustomId('close-ticket') .setLabel('Click here if you no longer need help') diff --git a/src/modals/sync-name-modal.ts b/src/modals/sync-name-modal.ts index 93e4760..d12d484 100644 --- a/src/modals/sync-name-modal.ts +++ b/src/modals/sync-name-modal.ts @@ -1,5 +1,4 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, GuildTextBasedChannel, MessageFlags, ModalSubmitInteraction, TextChannel, ThreadAutoArchiveDuration } from 'discord.js'; -import { ticketCooldownMap } from '../shared/ticket-cooldown'; import { Database } from '../shared/Database'; import { deferInteraction, logCustomEvent, syncName } from '../utils'; import { config } from '../config'; diff --git a/src/shared/Database.ts b/src/shared/Database.ts index 06eca7c..e57d933 100644 --- a/src/shared/Database.ts +++ b/src/shared/Database.ts @@ -1,9 +1,7 @@ import sqlite3 from 'sqlite3'; import { open, Database as SqliteDatabase } from 'sqlite'; -import { config } from '../config'; -import DiscordOAuth2 from 'discord-oauth2'; import { serializeMessage, wait } from '../utils'; -import { GuildSettings, LoggedMessage, TicketMessage, TicketPhrase, Note, Ban, GuildArraySetting, GithubUserMapping, KnowledgebaseItem } from '../types'; +import { GuildSettings, LoggedMessage, TicketMessage, TicketPhrase, Note, Ban, GuildArraySetting, GithubUserMapping, KnowledgebaseItem, PrivateHelpTicket } from '../types'; import { Message } from '../events'; const DB_SCHEMA = ` @@ -67,6 +65,8 @@ const DB_SCHEMA = ` timestamp datetime NOT NULL DEFAULT (datetime('now', 'localtime')) ); + CREATE INDEX IF NOT EXISTS index_user_ids ON notes (user_id); + CREATE TABLE IF NOT EXISTS note_edits ( id INTEGER PRIMARY KEY, note_id INTEGER, @@ -93,9 +93,24 @@ const DB_SCHEMA = ` guild_id TEXT NOT NULL, name TEXT NOT NULL, content TEXT NOT NULL - ) + ); + + CREATE TABLE IF NOT EXISTS private_help_tickets ( + id INTEGER PRIMARY KEY, + thread_id TEXT NOT NULL, + user_id TEXT NOT NULL, + status INTEGER NOT NULL, + timestamp datetime NOT NULL DEFAULT (datetime('now', 'localtime')) + ); + + CREATE INDEX IF NOT EXISTS index_timestamp ON private_help_tickets (timestamp); `; +export const enum PrivateHelpTicketStatus { + OPEN = 0, + CLOSED = 1 +} + export class Database { private static db: SqliteDatabase; @@ -434,4 +449,20 @@ export class Database { } // -- END KNOWLEDGEBASE -- + + // -- START PRIVATE HELP TICKETS -- + + static async createPrivateHelpTicket(userId: string, threadId: string) { + await Database.db.run('INSERT INTO private_help_tickets(user_id, thread_id, status) VALUES (?, ?, ?)', userId, threadId, PrivateHelpTicketStatus.OPEN); + } + + static async closePrivateHelpTicket(threadId: string) { + await Database.db.run('UPDATE private_help_tickets SET status = ? WHERE thread_id = ?', PrivateHelpTicketStatus.CLOSED, threadId); + } + + static async getLatestPrivateHelpTicketBy(userId: string): Promise { + return await Database.db.get('SELECT * from private_help_tickets WHERE user_id = ? ORDER BY timestamp DESC LIMIT 1', userId); + } + + // -- END PRIVATE HELP TICKETS } \ No newline at end of file diff --git a/src/shared/ticket-cooldown.ts b/src/shared/ticket-cooldown.ts deleted file mode 100644 index be8d8ba..0000000 --- a/src/shared/ticket-cooldown.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const ticketCooldownMap = new Map(); - -export function pruneOldTickets() { - const keys = Array.from(ticketCooldownMap.keys()); - for (const key of keys) { - if (Date.now() >= ticketCooldownMap.get(key)!) - ticketCooldownMap.delete(key); - } -} \ No newline at end of file diff --git a/src/types/database-types.d.ts b/src/types/database-types.d.ts index dfd0ee4..80986f6 100644 --- a/src/types/database-types.d.ts +++ b/src/types/database-types.d.ts @@ -65,4 +65,11 @@ export type KnowledgebaseItem = { guild_id: string name: string content: string +} + +export type PrivateHelpTicket = { + id: number + user_id: string + status: PrivateHelpTicketStatus, + timestamp: string } \ No newline at end of file diff --git a/src/types/index.d.ts b/src/types/index.d.ts index c1be8c3..45567b0 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,5 +1,5 @@ -export * from './command'; -export * from './database-types'; -export * from './e621-types'; -export * from './handler'; -export * from './helper-types'; +export * from './command.d'; +export * from './database-types.d'; +export * from './e621-types.d'; +export * from './handler.d'; +export * from './helper-types.d'; diff --git a/src/utils/discord-user-utils.ts b/src/utils/discord-user-utils.ts index b4219fe..b6a88a4 100644 --- a/src/utils/discord-user-utils.ts +++ b/src/utils/discord-user-utils.ts @@ -1,4 +1,5 @@ import { Client, Guild, User } from 'discord.js'; +import { Database, PrivateHelpTicketStatus } from '../shared/Database'; export async function resolveUser(client: Client, value: string, guild: Guild | null = null): Promise { let user: User | null | undefined = null; @@ -24,4 +25,12 @@ export async function resolveUser(client: Client, value: string, guild: Guild | } return user; +} + +export async function canOpenPrivateHelpTicket(id: string): Promise { + const latestTicket = await Database.getLatestPrivateHelpTicketBy(id); + + if (latestTicket && latestTicket.status == PrivateHelpTicketStatus.OPEN && Date.now() - new Date(latestTicket.timestamp).getTime() < 8.64e+7) return false; + + return true; } \ No newline at end of file