diff --git a/src/commands/Core/help.js b/src/commands/Core/help.js index 5e0a5803..34d2fbcc 100644 --- a/src/commands/Core/help.js +++ b/src/commands/Core/help.js @@ -1,4 +1,4 @@ -import { +ο»Ώimport { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, @@ -44,7 +44,7 @@ const CATEGORY_ICONS = { -async function createInitialHelpMenu() { +async function createInitialHelpMenu(client) { const commandsPath = path.join(__dirname, "../../commands"); const categoryDirs = ( await fs.readdir(commandsPath, { withFileTypes: true }) @@ -72,8 +72,9 @@ async function createInitialHelpMenu() { }), ]; + const botName = client?.user?.username || "Bot"; const embed = createEmbed({ - title: "πŸ€– TitanBot Help Center", + title: `πŸ€– ${botName} Help Center`, description: "Your all-in-one Discord companion for moderation, economy, fun, and server management.", color: 'primary' }); @@ -204,7 +205,7 @@ export default { const { MessageFlags } = await import('discord.js'); await InteractionHelper.safeDefer(interaction); - const { embeds, components } = await createInitialHelpMenu(); + const { embeds, components } = await createInitialHelpMenu(client); await InteractionHelper.safeEditReply(interaction, { embeds, diff --git a/src/commands/Giveaway/gend.js b/src/commands/Giveaway/gend.js index f430da9b..0427b59a 100644 --- a/src/commands/Giveaway/gend.js +++ b/src/commands/Giveaway/gend.js @@ -139,9 +139,11 @@ export default { const winnerMentions = winners .map((id) => `<@${id}>`) .join(", "); - await channel.send({ + const winnerPingMsg = await channel.send({ content: `πŸŽ‰ CONGRATULATIONS ${winnerMentions}! You won the **${updatedGiveaway.prize}** giveaway! Please contact the host <@${updatedGiveaway.hostId}> to claim your prize.`, }); + updatedGiveaway.winnerPingMessageId = winnerPingMsg.id; + await saveGiveaway(interaction.client, interaction.guildId, updatedGiveaway); logger.info(`Giveaway ended with ${winners.length} winner(s): ${messageId}`); diff --git a/src/commands/Giveaway/greroll.js b/src/commands/Giveaway/greroll.js index da63c4a3..4207c125 100644 --- a/src/commands/Giveaway/greroll.js +++ b/src/commands/Giveaway/greroll.js @@ -160,9 +160,20 @@ export default { .map((id) => `<@${id}>`) .join(", "); - await channel.send({ - content: `πŸ”„ **GIVEAWAY REROLL** πŸ”„ New winners for **${giveaway.prize}**: ${winnerMentions}!`, - }); + // Edit the original winner ping if it still exists, otherwise send a new one + const existingPingMsg = giveaway.winnerPingMessageId + ? await channel.messages.fetch(giveaway.winnerPingMessageId).catch(() => null) + : null; + if (existingPingMsg) { + await existingPingMsg.edit({ + content: `πŸ”„ **GIVEAWAY REROLL** πŸ”„ New winners for **${giveaway.prize}**: ${winnerMentions}!`, + }); + } else { + const newPingMsg = await channel.send({ + content: `πŸ”„ **GIVEAWAY REROLL** πŸ”„ New winners for **${giveaway.prize}**: ${winnerMentions}!`, + }); + updatedGiveaway.winnerPingMessageId = newPingMsg.id; + } logger.info(`Giveaway rerolled (message not found, but announced): ${messageId}`); @@ -229,9 +240,20 @@ export default { .map((id) => `<@${id}>`) .join(", "); - await channel.send({ - content: `πŸ”„ **REROLL WINNERS** πŸ”„ CONGRATULATIONS ${winnerMentions}! You are the new winner(s) for the **${giveaway.prize}** giveaway! Please contact the host <@${giveaway.hostId}> to claim your prize.`, - }); + // Edit the original winner ping if it still exists, otherwise send a new one + const existingPingMsg = giveaway.winnerPingMessageId + ? await channel.messages.fetch(giveaway.winnerPingMessageId).catch(() => null) + : null; + if (existingPingMsg) { + await existingPingMsg.edit({ + content: `πŸ”„ **REROLL WINNERS** πŸ”„ CONGRATULATIONS ${winnerMentions}! You are the new winner(s) for the **${giveaway.prize}** giveaway! Please contact the host <@${giveaway.hostId}> to claim your prize.`, + }); + } else { + const newPingMsg = await channel.send({ + content: `πŸ”„ **REROLL WINNERS** πŸ”„ CONGRATULATIONS ${winnerMentions}! You are the new winner(s) for the **${giveaway.prize}** giveaway! Please contact the host <@${giveaway.hostId}> to claim your prize.`, + }); + updatedGiveaway.winnerPingMessageId = newPingMsg.id; + } logger.info(`Giveaway successfully rerolled: ${messageId} with ${newWinners.length} new winners`); diff --git a/src/commands/Ticket/claim.js b/src/commands/Ticket/claim.js index f9ce05cb..e6594249 100644 --- a/src/commands/Ticket/claim.js +++ b/src/commands/Ticket/claim.js @@ -1,8 +1,6 @@ import { getColor } from '../../config/bot.js'; -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, MessageFlags } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; -import { claimTicket } from '../../services/ticket.js'; -import { logEvent } from '../../utils/moderation.js'; +import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logger } from '../../utils/logger.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; @@ -82,16 +80,6 @@ export default { commandName: 'claim' }); - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: "Ticket Claimed", - target: channel.toString(), - executor: interaction.user.toString() - } - }); - } catch (error) { logger.error('Error executing claim command', { error: error.message, diff --git a/src/commands/Ticket/close.js b/src/commands/Ticket/close.js index 0bd6d539..35616717 100644 --- a/src/commands/Ticket/close.js +++ b/src/commands/Ticket/close.js @@ -1,8 +1,6 @@ import { getColor } from '../../config/bot.js'; -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, MessageFlags } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; -import { closeTicket } from '../../services/ticket.js'; -import { logEvent } from '../../utils/moderation.js'; +import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, MessageFlags } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logger } from '../../utils/logger.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; @@ -93,17 +91,6 @@ export default { commandName: 'close' }); - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: "Ticket Closed", - target: channel.toString(), - executor: interaction.user.toString(), - reason: reason - } - }); - } catch (error) { logger.error('Error executing close command', { error: error.message, diff --git a/src/commands/Ticket/priority.js b/src/commands/Ticket/priority.js index fbbc3ba6..8eac46b9 100644 --- a/src/commands/Ticket/priority.js +++ b/src/commands/Ticket/priority.js @@ -1,8 +1,6 @@ import { getColor } from '../../config/bot.js'; -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, MessageFlags } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; -import { updateTicketPriority } from '../../services/ticket.js'; -import { logEvent } from '../../utils/moderation.js'; +import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logger } from '../../utils/logger.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; @@ -98,17 +96,6 @@ export default { commandName: 'priority' }); - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: "Priority Updated", - target: interaction.channel.toString(), - executor: interaction.user.toString(), - reason: `Priority set to ${priorityLevel.toUpperCase()}` - } - }); - } catch (error) { logger.error('Error executing priority command', { error: error.message, diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 22ec056e..e3a1a501 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -2,7 +2,6 @@ import { getColor } from '../../config/bot.js'; import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js'; import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; import { getGuildConfig } from '../../services/guildConfig.js'; -import { logEvent } from '../../utils/moderation.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; import { logger } from '../../utils/logger.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; @@ -286,16 +285,7 @@ description: panelMessage, }, ); - logEvent({ - client, - guildId: interaction.guildId, - event: { - action: "Ticket System Setup", - target: panelChannel.toString(), - executor: interaction.user.toString(), - reason: "Ticket panel configuration" - } - }); + } catch (error) { logger.error('Ticket setup error', { error: error.message, diff --git a/src/commands/Welcome/goodbye.js b/src/commands/Welcome/goodbye.js index dcfd25e3..736924a5 100644 --- a/src/commands/Welcome/goodbye.js +++ b/src/commands/Welcome/goodbye.js @@ -62,7 +62,7 @@ export default { const ping = options.getBoolean('ping') ?? false; const existingConfig = await getWelcomeConfig(client, guild.id); - if (hasGoodbyeSetup(existingConfig)) { + if (existingConfig?.goodbyeChannelId) { logger.info(`[Goodbye] Setup blocked because config already exists in channel ${existingConfig.goodbyeChannelId} for guild ${guild.id}`); return await InteractionHelper.safeEditReply(interaction, { embeds: [errorEmbed( diff --git a/src/commands/Welcome/welcome.js b/src/commands/Welcome/welcome.js index 02d864b6..4461b0ba 100644 --- a/src/commands/Welcome/welcome.js +++ b/src/commands/Welcome/welcome.js @@ -67,7 +67,7 @@ export default { const ping = options.getBoolean('ping') ?? false; const existingConfig = await getWelcomeConfig(client, guild.id); - if (hasWelcomeSetup(existingConfig)) { + if (existingConfig?.channelId) { logger.info(`[Welcome] Setup blocked because config already exists in channel ${existingConfig.channelId} for guild ${guild.id}`); return await InteractionHelper.safeEditReply(interaction, { embeds: [errorEmbed( diff --git a/src/handlers/ticketButtons.js b/src/handlers/ticketButtons.js index c28c5b6f..0b4e8482 100644 --- a/src/handlers/ticketButtons.js +++ b/src/handlers/ticketButtons.js @@ -2,7 +2,6 @@ import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, Attac import { createEmbed, errorEmbed, successEmbed } from '../utils/embeds.js'; import { createTicket, closeTicket, claimTicket, updateTicketPriority } from '../services/ticket.js'; import { getGuildConfig } from '../services/guildConfig.js'; -import { logEvent } from '../utils/moderation.js'; import { logTicketEvent } from '../utils/ticketLogging.js'; import { logger } from '../utils/logger.js'; import { InteractionHelper } from '../utils/interactionHelper.js'; @@ -352,16 +351,6 @@ const claimTicketHandler = { embeds: [successEmbed('Ticket Claimed', 'You have successfully claimed this ticket!')], flags: MessageFlags.Ephemeral }); - - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: 'Ticket Claimed', - target: interaction.channel.toString(), - executor: interaction.user.toString() - } - }); } else { await interaction.editReply({ embeds: [errorEmbed('Error', result.error || 'Failed to claim ticket.')], @@ -490,11 +479,11 @@ const pinTicketHandler = { } // Check if channel name already has ping emoji - const hasPingEmoji = channel.name.startsWith('πŸ“'); + const hasPingEmoji = channel.name.startsWith('πŸ“Œ'); if (hasPingEmoji) { // Unpin: remove emoji and update position - const newName = channel.name.replace(/^πŸ“\s*/, ''); + const newName = channel.name.replace(/^πŸ“Œ\s*/, ''); await channel.edit({ name: newName, position: 999 // Move to end @@ -502,7 +491,7 @@ const pinTicketHandler = { await interaction.editReply({ embeds: [createEmbed({ - title: 'πŸ“ Ticket Unpinned', + title: 'πŸ“Œ Ticket Unpinned', description: 'This ticket has been unpinned and moved back to normal position.', color: 0x95A5A6 })], @@ -517,7 +506,7 @@ const pinTicketHandler = { }); } else { // Pin: add emoji and update position - const newName = `πŸ“ ${channel.name}`; + const newName = `πŸ“Œ ${channel.name}`; await channel.edit({ name: newName, position: 0 // Move to top @@ -525,7 +514,7 @@ const pinTicketHandler = { await interaction.editReply({ embeds: [createEmbed({ - title: 'πŸ“ Ticket Pinned', + title: 'πŸ“Œ Ticket Pinned', description: 'This ticket has been pinned to the top of the category.', color: 0x3498db })], @@ -551,7 +540,7 @@ const pinTicketHandler = { executorId: interaction.user.id, metadata: { isPinned: !hasPingEmoji, - newChannelName: hasPingEmoji ? channel.name.replace(/^πŸ“\s*/, '') : `πŸ“ ${channel.name}` + newChannelName: hasPingEmoji ? channel.name.replace(/^πŸ“Œ\s*/, '') : `πŸ“Œ ${channel.name}` } } }); @@ -608,16 +597,6 @@ const unclaimTicketHandler = { embeds: [successEmbed('Ticket Unclaimed', 'You have successfully unclaimed this ticket!')], flags: MessageFlags.Ephemeral }); - - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: 'Ticket Unclaimed', - target: interaction.channel.toString(), - executor: interaction.user.toString() - } - }); } else { await interaction.editReply({ embeds: [errorEmbed('Error', result.error || 'Failed to unclaim ticket.')], @@ -681,16 +660,6 @@ const reopenTicketHandler = { embeds: [successEmbed('Ticket Reopened', reopenMessage)], flags: MessageFlags.Ephemeral }); - - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: 'Ticket Reopened', - target: interaction.channel.toString(), - executor: interaction.user.toString() - } - }); } else { await interaction.editReply({ embeds: [errorEmbed('Error', result.error || 'Failed to reopen ticket.')], @@ -750,16 +719,6 @@ const deleteTicketHandler = { embeds: [successEmbed('Ticket Deleted', 'This ticket will be permanently deleted in 3 seconds.')], flags: MessageFlags.Ephemeral }); - - await logEvent({ - client, - guildId: interaction.guildId, - event: { - action: 'Ticket Deleted', - target: interaction.channel.toString(), - executor: interaction.user.toString() - } - }); } else { await interaction.editReply({ embeds: [errorEmbed('Error', result.error || 'Failed to delete ticket.')], diff --git a/src/services/giveawayService.js b/src/services/giveawayService.js index 8062cb67..10017bc4 100644 --- a/src/services/giveawayService.js +++ b/src/services/giveawayService.js @@ -475,7 +475,9 @@ export async function checkGiveaways(client) { if (winners.length > 0) { const winnerAnnouncement = `πŸŽ‰ Congratulations ${winnerMentions}! You won the **${giveaway.prize || 'giveaway'}**! Please contact <@${giveaway.hostId}> to claim your prize.`; - await channel.send({ content: winnerAnnouncement }); + const winnerPingMsg = await channel.send({ content: winnerAnnouncement }); + giveaway.winnerPingMessageId = winnerPingMsg.id; + await markGiveawayEnded(client, giveawayId, giveaway); try { diff --git a/src/services/ticket.js b/src/services/ticket.js index b38387bd..bf876b86 100644 --- a/src/services/ticket.js +++ b/src/services/ticket.js @@ -10,7 +10,7 @@ import { AttachmentBuilder, } from 'discord.js'; import { getGuildConfig } from './guildConfig.js'; -import { getTicketData, saveTicketData, deleteTicketData, getOpenTicketCountForUser } from '../utils/database.js'; +import { getTicketData, saveTicketData, deleteTicketData, getOpenTicketCountForUser, incrementTicketCounter } from '../utils/database.js'; import { logger } from '../utils/logger.js'; import { createEmbed, errorEmbed } from '../utils/embeds.js'; import { logTicketEvent } from '../utils/ticketLogging.js'; @@ -46,8 +46,6 @@ function getPriorityMap() { const PRIORITY_MAP = getPriorityMap(); const TICKET_DELETE_DELAY_MS = 3000; const TICKET_DELETE_DELAY_SECONDS = Math.floor(TICKET_DELETE_DELAY_MS / 1000); -const TICKET_NUMBER_BASE = 100; -const TICKET_NUMBER_RANGE = 900; @@ -1134,8 +1132,7 @@ export async function unclaimTicket(channel, unclaimer) { } async function getNextTicketNumber(guildId) { - const randomTicket = Math.floor(Math.random() * TICKET_NUMBER_RANGE) + TICKET_NUMBER_BASE; - return randomTicket.toString(); + return await incrementTicketCounter(guildId); } export async function updateTicketPriority(channel, priority, updater) { diff --git a/src/utils/database.js b/src/utils/database.js index f59c240f..b09da839 100644 --- a/src/utils/database.js +++ b/src/utils/database.js @@ -676,6 +676,35 @@ export async function deleteTicketData(guildId, channelId) { await db.delete(key); } +export function getTicketCounterKey(guildId) { + return `guild:${guildId}:ticket:counter`; +} + +export async function getTicketCounter(guildId) { + if (!db.initialized) { + await db.initialize(); + } + + const key = getTicketCounterKey(guildId); + const counter = await db.get(key); + return counter || 0; +} + +export async function incrementTicketCounter(guildId) { + if (!db.initialized) { + await db.initialize(); + } + + const key = getTicketCounterKey(guildId); + const currentCounter = await getTicketCounter(guildId); + const nextCounter = currentCounter + 1; + + await db.set(key, nextCounter); + + // Return padded to 3 digits (001, 002, etc.) + return nextCounter.toString().padStart(3, '0'); +} + @@ -745,6 +774,7 @@ function normalizeWelcomeConfig(raw = {}) { leaveMessage, leaveEmbed, dmMessage: base.dmMessage ?? "", + goodbyePing: Boolean(base.goodbyePing), roleIds, autoRoleDelay: base.autoRoleDelay ?? 0, joinLogs: base.joinLogs ?? { enabled: false, channelId: null },