diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..aaffce8 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,94 @@ +# Architecture + +This file summarizes the runtime architecture and data model of the bot as implemented in the repository. It is derived directly from the code (index.js, utils, events, models). + +## High-level components + +- index.js + - Loads commands and events dynamically from `commands/` and `events/`. + - Initializes the `Database` singleton (`utils/database.js`) which wraps `models/schemas.js`. + - Starts `TaskScheduler` (`utils/scheduler.js`) for periodic jobs. + - Rotates bot presence and handles graceful shutdown and global error handlers. +- Command loader + - `deploy-commands.js` scans `commands/`, uses `SlashCommandBuilder` JSON via `command.data.toJSON()` and registers commands via Discord REST API. +- Events + - `events/interactionCreate.js` — central handler for slash commands, buttons, modals, select menus; enforces per-command cooldowns and handles many interactive flows (tickets, AI modals, truth-or-dare, feedback). + - `events/messageCreate.js` — XP tracking, message-based AI auto-responses (when configured), and related side-effects. + - Other events: `ready.js`, `guildMemberAdd.js`, `modalCreate.js`, `voiceStateUpdate.js`, `helpInteraction.js`. +- Database layer + - `utils/database.js` exposes model methods and utility functions for server config, user profile, tickets, AI rate limits, XP transactions, leaderboards, birthdays, shop items, anti-raid, and guild economy. + - Models defined in `models/schemas.js` (see Data Models below). +- Background scheduler + - `utils/scheduler.js` uses `node-cron` to run: + - daily resets (midnight UTC) + - weekly resets (Monday midnight UTC) + - hourly leaderboard updates + - role checks every 30 minutes + - birthday checks daily at 8:00 UTC + +## Data models (high-level) + +- ServerConfig — per-guild configuration: `aiEnabled`, `aiContext`, `aiChannels`, `aiMode`, `ticketCategoryId`, `ticketLogChannelId`, XP and role automation settings, birthday config, etc. +- UserProfile — per-user-per-guild profile: `userId`, `guildId`, `wallet`, `bank`, `totalXp`, `level`, `messageCount`, `voiceMinutes`, `points`, `currentRoles`, `dailyStreak`, timestamps. +- Ticket — support ticket records with messages array, status, priority, moderatorId, closedAt. +- AIRateLimit — per-user rate limiting for AI requests (requestCount, lastRequest, resetAt). +- XPTransaction — XP audit log entries. +- Leaderboard — cached top users per guild. +- Birthday — birthday records. +- GuildEconomy, ShopItem, TruthOrDareConfig, AntiRaid — specialized configuration schemas. + +(See `models/schemas.js` for full field lists and indexes.) + +## Runtime flow + +1. Startup (`node index.js`) + - `index.js` loads commands and events and attempts to connect to MongoDB using `MONGODB_URI`. + - Scheduler is created and registered. + - Bot logs in with `DISCORD_TOKEN`. +2. Interaction flow + - Slash commands: `events/interactionCreate.js` receives chat input commands, enforces cooldowns, and calls the command's `execute(interaction, client)`. + - Buttons/selects/modals: `interactionCreate` dispatches UI interactions to helper handlers (tickets, help navigation, AI modals, truth-or-dare buttons). +3. Message flow + - `messageCreate.js` handles XP awarding (rate-limited per user/time) and message-based AI auto-responses when the server `ServerConfig` allows it. +4. Scheduler tasks + - Cron jobs perform resets, leaderboard updates, and role assignment checks. + +## Command & event structure + +- Each command file exports at least `data` (SlashCommandBuilder) and `execute` function. +- Commands may include a `cooldown` property (seconds) used by the interaction handler. +- Commands are organized in folders (fun, moderation, economy, xp, ai, truth-or-dare, etc.). + +## External integrations + +- Discord (discord.js v14) — main runtime and components +- MongoDB (mongoose) — persistence +- Google Gemini via `@google/genai` — AI assistant features (requires `GEMINI_API_KEY`) +- node-cron — scheduling + +## Environment variables required by code + +- `DISCORD_TOKEN` (required) — bot token +- `CLIENT_ID` (required for `deploy-commands.js`) — application id +- `GUILD_ID` (optional) — when present `deploy-commands.js` deploys commands to that guild +- `MONGODB_URI` (required) — MongoDB connection string +- `GEMINI_API_KEY` (optional) — Google Gemini key for AI +- `PORT` (optional) — not used by default; an express server is present but commented out + +## Permissions & Intents + +- Intents requested in `index.js`: `Guilds`, `GuildMembers`, `GuildMessages`, `MessageContent`, `GuildMessageReactions`, `GuildVoiceStates`, `GuildPresences`. +- Privileged intents (Message Content, Guild Members, Presences) must be enabled in the Discord Developer Portal to use the corresponding features. +- Bot requires typical moderation permissions depending on features used (Ban/Kick, ManageChannels for tickets, ManageRoles for automatic role assignment, ManageMessages for purge, EmbedLinks + SendMessages). + +## Scaling & operational notes + +- Scheduler runs in-process; running multiple instances will duplicate scheduled jobs unless you coordinate (e.g., leader election or single scheduler process). +- Database connections are per-process (the `Database` singleton avoids re-connecting repeatedly in the same process). +- Consider using Redis or another cache if you need high-throughput leaderboards or cross-process rate-limiting. + +## Extending the bot + +- Add new commands: create a file in `commands//` exporting `data` and `execute`. Run `node deploy-commands.js` to register slash commands. +- Add new events: place a new file in `events/` exporting `{ name, execute, once? }` and the loader in `index.js` will attach it. +- New DB models: update `models/schemas.js` and expose them via `utils/database.js` for convenience. diff --git a/commands/antimodules/antiraid.js b/commands/antimodules/antiraid.js index fb3fc62..e6c6eb1 100644 --- a/commands/antimodules/antiraid.js +++ b/commands/antimodules/antiraid.js @@ -1,148 +1,162 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const Database = require("../../../utils/database"); +const Database = require("../../utils/database"); module.exports = { - data: new SlashCommandBuilder() - .setName("antiraid") - .setDescription("🛡️ Simple anti-raid protection") - .addSubcommand((subcommand) => - subcommand - .setName("enable") - .setDescription("Enable anti-raid protection") - ) - .addSubcommand((subcommand) => - subcommand - .setName("disable") - .setDescription("Disable anti-raid protection") - ) - .addSubcommand((subcommand) => - subcommand - .setName("status") - .setDescription("Check anti-raid status") - ), + data: new SlashCommandBuilder() + .setName("antiraid") + .setDescription("🛡️ Simple anti-raid protection") + .addSubcommand((subcommand) => + subcommand + .setName("enable") + .setDescription("Enable anti-raid protection"), + ) + .addSubcommand((subcommand) => + subcommand + .setName("disable") + .setDescription("Disable anti-raid protection"), + ) + .addSubcommand((subcommand) => + subcommand.setName("status").setDescription("Check anti-raid status"), + ), - async execute(interaction) { - if (!interaction.member.permissions.has("Administrator")) { - return interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ No Permission") - .setDescription("You need Administrator permissions to use this command.") - ], - flags: 64 - }); - } + async execute(interaction) { + if (!interaction.member.permissions.has("Administrator")) { + return interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ No Permission") + .setDescription( + "You need Administrator permissions to use this command.", + ), + ], + flags: 64, + }); + } - const subcommand = interaction.options.getSubcommand(); + const subcommand = interaction.options.getSubcommand(); - try { - const db = Database; // Use the exported instance -await db.ensureConnection(); // Ensure connection is established - - switch (subcommand) { - case "enable": - await handleEnable(interaction, db); - break; - case "disable": - await handleDisable(interaction, db); - break; - case "status": - await handleStatus(interaction, db); - break; - } - } catch (error) { - console.error("Anti-raid command error:", error); - - await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ Error") - .setDescription("An error occurred while processing anti-raid command.") - ], - flags: 64 - }); - } - }, + try { + const db = await Database.getInstance(); + await db.ensureConnection(); + + switch (subcommand) { + case "enable": + await handleEnable(interaction, db); + break; + case "disable": + await handleDisable(interaction, db); + break; + case "status": + await handleStatus(interaction, db); + break; + } + } catch (error) { + console.error("Anti-raid command error:", error); + + await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ Error") + .setDescription( + "An error occurred while processing anti-raid command.", + ), + ], + flags: 64, + }); + } + }, }; async function handleEnable(interaction, db) { - try { - await db.GuildConfig.findOneAndUpdate( - { guildId: interaction.guild.id }, - { - $set: { - antiRaidEnabled: true, - antiRaidThreshold: 5, - antiRaidTimeWindow: 10000 // 10 seconds - } - }, - { upsert: true, new: true } - ); + try { + await db.GuildConfig.findOneAndUpdate( + { guildId: interaction.guild.id }, + { + $set: { + antiRaidEnabled: true, + antiRaidThreshold: 5, + antiRaidTimeWindow: 10000, // 10 seconds + }, + }, + { upsert: true, new: true }, + ); - await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor("#00ff00") - .setTitle("✅ Anti-Raid Enabled") - .setDescription("Anti-raid protection has been enabled with default settings.") - .addFields( - { name: "Threshold", value: "5 joins", inline: true }, - { name: "Time Window", value: "10 seconds", inline: true } - ) - ] - }); - } catch (error) { - console.error("Error enabling anti-raid:", error); - throw error; - } + await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor("#00ff00") + .setTitle("✅ Anti-Raid Enabled") + .setDescription( + "Anti-raid protection has been enabled with default settings.", + ) + .addFields( + { name: "Threshold", value: "5 joins", inline: true }, + { name: "Time Window", value: "10 seconds", inline: true }, + ), + ], + }); + } catch (error) { + console.error("Error enabling anti-raid:", error); + throw error; + } } async function handleDisable(interaction, db) { - try { - await db.GuildConfig.findOneAndUpdate( - { guildId: interaction.guild.id }, - { $set: { antiRaidEnabled: false } }, - { upsert: true, new: true } - ); + try { + await db.GuildConfig.findOneAndUpdate( + { guildId: interaction.guild.id }, + { $set: { antiRaidEnabled: false } }, + { upsert: true, new: true }, + ); - await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ Anti-Raid Disabled") - .setDescription("Anti-raid protection has been disabled.") - ] - }); - } catch (error) { - console.error("Error disabling anti-raid:", error); - throw error; - } + await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ Anti-Raid Disabled") + .setDescription("Anti-raid protection has been disabled."), + ], + }); + } catch (error) { + console.error("Error disabling anti-raid:", error); + throw error; + } } async function handleStatus(interaction, db) { - try { - const config = await db.GuildConfig.findOne({ guildId: interaction.guild.id }); - - const isEnabled = config?.antiRaidEnabled || false; - const threshold = config?.antiRaidThreshold || 5; - const timeWindow = config?.antiRaidTimeWindow || 10000; + try { + const config = await db.GuildConfig.findOne({ + guildId: interaction.guild.id, + }); + + const isEnabled = config?.antiRaidEnabled || false; + const threshold = config?.antiRaidThreshold || 5; + const timeWindow = config?.antiRaidTimeWindow || 10000; - await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor(isEnabled ? "#00ff00" : "#ff0000") - .setTitle("🛡️ Anti-Raid Status") - .addFields( - { name: "Status", value: isEnabled ? "✅ Enabled" : "❌ Disabled", inline: true }, - { name: "Threshold", value: `${threshold} joins`, inline: true }, - { name: "Time Window", value: `${timeWindow / 1000} seconds`, inline: true } - ) - ] - }); - } catch (error) { - console.error("Error checking anti-raid status:", error); - throw error; - } + await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor(isEnabled ? "#00ff00" : "#ff0000") + .setTitle("🛡️ Anti-Raid Status") + .addFields( + { + name: "Status", + value: isEnabled ? "✅ Enabled" : "❌ Disabled", + inline: true, + }, + { name: "Threshold", value: `${threshold} joins`, inline: true }, + { + name: "Time Window", + value: `${timeWindow / 1000} seconds`, + inline: true, + }, + ), + ], + }); + } catch (error) { + console.error("Error checking anti-raid status:", error); + throw error; + } } diff --git a/commands/economy/bal.js b/commands/economy/bal.js index 5aa1111..845c210 100644 --- a/commands/economy/bal.js +++ b/commands/economy/bal.js @@ -1,61 +1,65 @@ -const { SlashCommandBuilder, EmbedBuilder, MessageFlags } = require("discord.js"); +const { + SlashCommandBuilder, + EmbedBuilder, + MessageFlags, +} = require("discord.js"); const Database = require("../../utils/database"); module.exports = { - data: new SlashCommandBuilder() - .setName("bal") - .setDescription("💰 Check your wallet and bank balance") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to check the balance of (optional)") - .setRequired(false) - ), - async execute(interaction) { - const db = Database; // Use the exported instance -await db.ensureConnection(); // Ensure connection is established - const targetUser = interaction.options.getUser("user") || interaction.user; - const guildId = interaction.guild.id; + data: new SlashCommandBuilder() + .setName("bal") + .setDescription("💰 Check your wallet and bank balance") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to check the balance of (optional)") + .setRequired(false), + ), + async execute(interaction) { + const db = await Database.getInstance(); + await db.ensureConnection(); + const targetUser = interaction.options.getUser("user") || interaction.user; + const guildId = interaction.guild.id; - await interaction.deferReply(); + await interaction.deferReply(); - const profile = await db.getUserProfile(targetUser.id, guildId); + const profile = await db.getUserProfile(targetUser.id, guildId); - if (!profile) { - return await interaction.editReply({ - content: `❌ No profile data found for ${targetUser.username}.`, - }); - } + if (!profile) { + return await interaction.editReply({ + content: `❌ No profile data found for ${targetUser.username}.`, + }); + } - const balanceEmbed = new EmbedBuilder() - .setColor("#FFD700") // Gold color for economy! - .setTitle(`💰 ${targetUser.username}'s Balance`) - .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) - .addFields( - { - name: "👛 Wallet", - value: `**${(profile.wallet || 0).toLocaleString()}** coins`, - inline: true, - }, - { - name: "🏦 Bank", - value: `**${(profile.bank || 0).toLocaleString()}** coins`, - inline: true, - }, - { - name: "📊 Total", - value: `**${( - (profile.wallet || 0) + (profile.bank || 0) - ).toLocaleString()}** coins`, - inline: true, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const balanceEmbed = new EmbedBuilder() + .setColor("#FFD700") // Gold color for economy! + .setTitle(`💰 ${targetUser.username}'s Balance`) + .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) + .addFields( + { + name: "👛 Wallet", + value: `**${(profile.wallet || 0).toLocaleString()}** coins`, + inline: true, + }, + { + name: "🏦 Bank", + value: `**${(profile.bank || 0).toLocaleString()}** coins`, + inline: true, + }, + { + name: "📊 Total", + value: `**${( + (profile.wallet || 0) + (profile.bank || 0) + ).toLocaleString()}** coins`, + inline: true, + }, + ) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.editReply({ embeds: [balanceEmbed] }); - }, -}; \ No newline at end of file + await interaction.editReply({ embeds: [balanceEmbed] }); + }, +}; diff --git a/commands/fun/8ball.js b/commands/fun/8ball.js index adb441d..617a7bf 100644 --- a/commands/fun/8ball.js +++ b/commands/fun/8ball.js @@ -1,107 +1,107 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { getRandomResponse } = require("@adb/server/utils/helpers"); +const { getRandomResponse } = require("../../utils/helpers"); module.exports = { - data: new SlashCommandBuilder() - .setName("8ball") - .setDescription( - "🎱 Ask the magic 8-ball a question and receive mystical wisdom" - ) - .addStringOption((option) => - option - .setName("question") - .setDescription("The question you want to ask the magic 8-ball") - .setRequired(true) - ), - cooldown: 3, - async execute(interaction, client) { - const question = interaction.options.getString("question"); + data: new SlashCommandBuilder() + .setName("8ball") + .setDescription( + "🎱 Ask the magic 8-ball a question and receive mystical wisdom", + ) + .addStringOption((option) => + option + .setName("question") + .setDescription("The question you want to ask the magic 8-ball") + .setRequired(true), + ), + cooldown: 3, + async execute(interaction, client) { + const question = interaction.options.getString("question"); - // 🎱 Magic 8-ball responses with personality - const responses = [ - // Positive responses - "✨ It is certain", - "🌟 Without a doubt", - "💫 Yes definitely", - "🎯 You may rely on it", - "🚀 As I see it, yes", - "⭐ Most likely", - "🎉 Outlook good", - "💎 Yes", - "🏆 Signs point to yes", + // 🎱 Magic 8-ball responses with personality + const responses = [ + // Positive responses + "✨ It is certain", + "🌟 Without a doubt", + "💫 Yes definitely", + "🎯 You may rely on it", + "🚀 As I see it, yes", + "⭐ Most likely", + "🎉 Outlook good", + "💎 Yes", + "🏆 Signs point to yes", - // Negative responses - "❌ Don't count on it", - "🚫 My reply is no", - "💔 My sources say no", - "⛔ Outlook not so good", - "🌑 Very doubtful", - "❎ No way", - "🔒 Absolutely not", + // Negative responses + "❌ Don't count on it", + "🚫 My reply is no", + "💔 My sources say no", + "⛔ Outlook not so good", + "🌑 Very doubtful", + "❎ No way", + "🔒 Absolutely not", - // Neutral/uncertain responses - "🤔 Reply hazy, try again", - "💭 Ask again later", - "🌀 Better not tell you now", - "⏳ Cannot predict now", - "🔮 Concentrate and ask again", - "🎭 The future is unclear", - "🌊 Signs are mixed", - "⚖️ Could go either way", - ]; + // Neutral/uncertain responses + "🤔 Reply hazy, try again", + "💭 Ask again later", + "🌀 Better not tell you now", + "⏳ Cannot predict now", + "🔮 Concentrate and ask again", + "🎭 The future is unclear", + "🌊 Signs are mixed", + "⚖️ Could go either way", + ]; - const response = getRandomResponse(responses); + const response = getRandomResponse(responses); - // 🎨 Color based on response type - let embedColor; - if ( - response.includes("✨") || - response.includes("🌟") || - response.includes("💫") || - response.includes("🎯") || - response.includes("🚀") || - response.includes("⭐") || - response.includes("🎉") || - response.includes("💎") || - response.includes("🏆") - ) { - embedColor = client.colors.success; - } else if ( - response.includes("❌") || - response.includes("🚫") || - response.includes("💔") || - response.includes("⛔") || - response.includes("🌑") || - response.includes("❎") || - response.includes("🔒") - ) { - embedColor = client.colors.error; - } else { - embedColor = client.colors.warning; - } + // 🎨 Color based on response type + let embedColor; + if ( + response.includes("✨") || + response.includes("🌟") || + response.includes("💫") || + response.includes("🎯") || + response.includes("🚀") || + response.includes("⭐") || + response.includes("🎉") || + response.includes("💎") || + response.includes("🏆") + ) { + embedColor = client.colors.success; + } else if ( + response.includes("❌") || + response.includes("🚫") || + response.includes("💔") || + response.includes("⛔") || + response.includes("🌑") || + response.includes("❎") || + response.includes("🔒") + ) { + embedColor = client.colors.error; + } else { + embedColor = client.colors.warning; + } - const eightBallEmbed = new EmbedBuilder() - .setColor(embedColor) - .setTitle("🎱 Magic 8-Ball Oracle") - .addFields( - { - name: "❓ Your Question", - value: `*"${question}"*`, - inline: false, - }, - { - name: "🔮 The 8-Ball Says...", - value: `**${response}**`, - inline: false, - } - ) - .setThumbnail("https://cdn.discordapp.com/emojis/🎱.png") - .setFooter({ - text: `Asked by ${interaction.user.tag} • The magic 8-ball has spoken!`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const eightBallEmbed = new EmbedBuilder() + .setColor(embedColor) + .setTitle("🎱 Magic 8-Ball Oracle") + .addFields( + { + name: "❓ Your Question", + value: `*"${question}"*`, + inline: false, + }, + { + name: "🔮 The 8-Ball Says...", + value: `**${response}**`, + inline: false, + }, + ) + .setThumbnail("https://cdn.discordapp.com/emojis/🎱.png") + .setFooter({ + text: `Asked by ${interaction.user.tag} • The magic 8-ball has spoken!`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.reply({ embeds: [eightBallEmbed] }); - }, + await interaction.reply({ embeds: [eightBallEmbed] }); + }, }; diff --git a/commands/fun/secret.js b/commands/fun/secret.js index ae0b7f7..978286d 100644 --- a/commands/fun/secret.js +++ b/commands/fun/secret.js @@ -1,143 +1,143 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { getRandomResponse } = require("@adb/server/utils/helpers"); +const { getRandomResponse } = require("../../utils/helpers"); module.exports = { - data: new SlashCommandBuilder() - .setName("secret") - .setDescription("🤫 Discover a hidden easter egg... if you dare!"), - cooldown: 30, - async execute(interaction, client) { - // 🎭 Array of easter egg responses - const easterEggs = [ - { - title: "🕵️‍♂️ Secret Agent Mode Activated!", - description: - "You've discovered the secret command! Your mission, should you choose to accept it, is to have an awesome day! 🎯", - color: client.colors.primary, - field: { - name: "🎪 Fun Fact", - value: "This message will self-destruct in... just kidding! 😄", - }, - }, - { - title: "🏴‍☠️ Ahoy, Treasure Hunter!", - description: - "X marks the spot! You've found the hidden treasure of... absolutely nothing! But hey, at least you found it! 🗺️", - color: client.colors.warning, - field: { - name: "💰 Reward", - value: "The real treasure was the commands we ran along the way! ⚡", - }, - }, - { - title: "🚀 Houston, We Have Contact!", - description: - "Congratulations, space explorer! You've discovered this secret transmission from the NovaBot mothership! 🛸", - color: client.colors.success, - field: { - name: "📡 Message", - value: "The aliens say: 'Hello, human! You're pretty cool!' 👽", - }, - }, - { - title: "🧙‍♂️ Magic Spell Discovered!", - description: - "You've cast the ancient spell of curiosity! *Abracadabra!* ✨ Your reward is this magical message!", - color: client.colors.error, - field: { - name: "🔮 Prophecy", - value: - "A great destiny awaits those who read this message... or maybe just a good day! 🌟", - }, - }, - { - title: "🎮 Achievement Unlocked!", - description: - "**Secret Finder** - Found the hidden easter egg command! You're officially a NovaBot power user! 🏆", - color: client.colors.primary, - field: { - name: "🎯 Progress", - value: "1/1 Secret Commands Found • Master Level: Achieved! 🥇", - }, - }, - { - title: "🎪 Welcome to the Secret Society!", - description: - "You are now a member of the exclusive 'I Found The Secret Command' club! Membership perks include... well, this message! 🎉", - color: client.colors.success, - field: { - name: "🤝 Members", - value: - "You + Everyone else who found this = Best Friends Forever! 💫", - }, - }, - ]; + data: new SlashCommandBuilder() + .setName("secret") + .setDescription("🤫 Discover a hidden easter egg... if you dare!"), + cooldown: 30, + async execute(interaction, client) { + // 🎭 Array of easter egg responses + const easterEggs = [ + { + title: "🕵️‍♂️ Secret Agent Mode Activated!", + description: + "You've discovered the secret command! Your mission, should you choose to accept it, is to have an awesome day! 🎯", + color: client.colors.primary, + field: { + name: "🎪 Fun Fact", + value: "This message will self-destruct in... just kidding! 😄", + }, + }, + { + title: "🏴‍☠️ Ahoy, Treasure Hunter!", + description: + "X marks the spot! You've found the hidden treasure of... absolutely nothing! But hey, at least you found it! 🗺️", + color: client.colors.warning, + field: { + name: "💰 Reward", + value: "The real treasure was the commands we ran along the way! ⚡", + }, + }, + { + title: "🚀 Houston, We Have Contact!", + description: + "Congratulations, space explorer! You've discovered this secret transmission from the NovaBot mothership! 🛸", + color: client.colors.success, + field: { + name: "📡 Message", + value: "The aliens say: 'Hello, human! You're pretty cool!' 👽", + }, + }, + { + title: "🧙‍♂️ Magic Spell Discovered!", + description: + "You've cast the ancient spell of curiosity! *Abracadabra!* ✨ Your reward is this magical message!", + color: client.colors.error, + field: { + name: "🔮 Prophecy", + value: + "A great destiny awaits those who read this message... or maybe just a good day! 🌟", + }, + }, + { + title: "🎮 Achievement Unlocked!", + description: + "**Secret Finder** - Found the hidden easter egg command! You're officially a NovaBot power user! 🏆", + color: client.colors.primary, + field: { + name: "🎯 Progress", + value: "1/1 Secret Commands Found • Master Level: Achieved! 🥇", + }, + }, + { + title: "🎪 Welcome to the Secret Society!", + description: + "You are now a member of the exclusive 'I Found The Secret Command' club! Membership perks include... well, this message! 🎉", + color: client.colors.success, + field: { + name: "🤝 Members", + value: + "You + Everyone else who found this = Best Friends Forever! 💫", + }, + }, + ]; - const selectedEgg = getRandomResponse(easterEggs); + const selectedEgg = getRandomResponse(easterEggs); - // 🎨 Create the easter egg embed - const easterEggEmbed = new EmbedBuilder() - .setColor(selectedEgg.color) - .setTitle(selectedEgg.title) - .setDescription(selectedEgg.description) - .addFields(selectedEgg.field) - .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 256 })) - .setFooter({ - text: `Secret discovered by ${interaction.user.tag} • Shh, don't tell anyone! 🤫`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + // 🎨 Create the easter egg embed + const easterEggEmbed = new EmbedBuilder() + .setColor(selectedEgg.color) + .setTitle(selectedEgg.title) + .setDescription(selectedEgg.description) + .addFields(selectedEgg.field) + .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 256 })) + .setFooter({ + text: `Secret discovered by ${interaction.user.tag} • Shh, don't tell anyone! 🤫`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - // 🎭 Random bonus messages - const bonusMessages = [ - "P.S. You're awesome! 😎", - "P.S. The cake is NOT a lie! 🍰", - "P.S. 42 is indeed the answer! 🌌", - "P.S. May the force be with you! ⭐", - "P.S. Keep being curious! 🔍", - "P.S. You have excellent taste in commands! 👌", - ]; + // 🎭 Random bonus messages + const bonusMessages = [ + "P.S. You're awesome! 😎", + "P.S. The cake is NOT a lie! 🍰", + "P.S. 42 is indeed the answer! 🌌", + "P.S. May the force be with you! ⭐", + "P.S. Keep being curious! 🔍", + "P.S. You have excellent taste in commands! 👌", + ]; - const bonusMessage = getRandomResponse(bonusMessages); - easterEggEmbed.addFields({ - name: "🎁 Bonus", - value: bonusMessage, - inline: false, - }); + const bonusMessage = getRandomResponse(bonusMessages); + easterEggEmbed.addFields({ + name: "🎁 Bonus", + value: bonusMessage, + inline: false, + }); - await interaction.reply({ - embeds: [easterEggEmbed], - flags: 64, // MessageFlags.Ephemeral - }); + await interaction.reply({ + embeds: [easterEggEmbed], + flags: 64, // MessageFlags.Ephemeral + }); - // 🎊 Log the discovery - console.log( - `🤫 ${interaction.user.tag} discovered the secret command in ${ - interaction.guild?.name || "DM" - }` - ); + // 🎊 Log the discovery + console.log( + `🤫 ${interaction.user.tag} discovered the secret command in ${ + interaction.guild?.name || "DM" + }`, + ); - // 🎲 Small chance for extra surprise - if (Math.random() < 0.1) { - // 10% chance - setTimeout(async () => { - try { - const surpriseEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("🎉 BONUS SURPRISE!") - .setDescription( - "You hit the 10% bonus chance! Here's a virtual high-five! ✋" - ) - .setFooter({ text: "Your luck stat must be maxed out!" }); + // 🎲 Small chance for extra surprise + if (Math.random() < 0.1) { + // 10% chance + setTimeout(async () => { + try { + const surpriseEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("🎉 BONUS SURPRISE!") + .setDescription( + "You hit the 10% bonus chance! Here's a virtual high-five! ✋", + ) + .setFooter({ text: "Your luck stat must be maxed out!" }); - await interaction.followUp({ - embeds: [surpriseEmbed], - flags: 64, // MessageFlags.Ephemeral - }); - } catch (error) { - console.error("❌ Error sending bonus surprise:", error); - } - }, 3000); - } - }, + await interaction.followUp({ + embeds: [surpriseEmbed], + flags: 64, // MessageFlags.Ephemeral + }); + } catch (error) { + console.error("❌ Error sending bonus surprise:", error); + } + }, 3000); + } + }, }; diff --git a/commands/general/botstats.js b/commands/general/botstats.js index 3334a31..2b9f1fc 100644 --- a/commands/general/botstats.js +++ b/commands/general/botstats.js @@ -1,104 +1,104 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); const { - formatUptime, - formatBytes, - generateProgressBar, -} = require("@adb/server/utils/helpers"); + formatUptime, + formatBytes, + generateProgressBar, +} = require("../../utils/helpers"); module.exports = { - data: new SlashCommandBuilder() - .setName("botstats") - .setDescription("📊 View detailed bot performance statistics and metrics"), - cooldown: 5, - async execute(interaction, client) { - // 📊 Memory usage - const memUsage = process.memoryUsage(); - const totalMem = require("os").totalmem(); - const usedMem = memUsage.heapUsed; + data: new SlashCommandBuilder() + .setName("botstats") + .setDescription("📊 View detailed bot performance statistics and metrics"), + cooldown: 5, + async execute(interaction, client) { + // 📊 Memory usage + const memUsage = process.memoryUsage(); + const totalMem = require("os").totalmem(); + const usedMem = memUsage.heapUsed; - // 🕒 Uptime calculation - const uptime = process.uptime() * 1000; + // 🕒 Uptime calculation + const uptime = process.uptime() * 1000; - // 📈 Performance metrics - const guilds = client.guilds.cache.size; - const users = client.users.cache.size; - const channels = client.channels.cache.size; - const commands = client.commands.size; + // 📈 Performance metrics + const guilds = client.guilds.cache.size; + const users = client.users.cache.size; + const channels = client.channels.cache.size; + const commands = client.commands.size; - // 🎯 CPU usage approximation - const cpuUsage = process.cpuUsage(); - const cpuPercent = - Math.round(((cpuUsage.user + cpuUsage.system) / 1000000) * 100) / 100; + // 🎯 CPU usage approximation + const cpuUsage = process.cpuUsage(); + const cpuPercent = + Math.round(((cpuUsage.user + cpuUsage.system) / 1000000) * 100) / 100; - const statsEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("📊 NovaBot Performance Dashboard") - .setDescription("Real-time bot statistics and performance metrics") - .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 256 })) - .addFields( - { - name: "⏱️ Uptime", - value: `\`${formatUptime(uptime)}\``, - inline: true, - }, - { - name: "🏓 Latency", - value: `\`${client.ws.ping}ms\``, - inline: true, - }, - { - name: "🎯 Commands", - value: `\`${commands}\``, - inline: true, - }, - { - name: "🏰 Servers", - value: `\`${guilds.toLocaleString()}\``, - inline: true, - }, - { - name: "👥 Users", - value: `\`${users.toLocaleString()}\``, - inline: true, - }, - { - name: "📢 Channels", - value: `\`${channels.toLocaleString()}\``, - inline: true, - }, - { - name: "💾 Memory Usage", - value: `\`${formatBytes(usedMem)}\` / \`${formatBytes( - totalMem - )}\`\n${generateProgressBar(usedMem, totalMem)} ${Math.round( - (usedMem / totalMem) * 100 - )}%`, - inline: false, - }, - { - name: "🖥️ System Info", - value: `**Platform:** ${process.platform}\n**Node.js:** ${ - process.version - }\n**Discord.js:** v${require("discord.js").version}`, - inline: true, - }, - { - name: "⚡ Performance", - value: `**CPU Usage:** ~${cpuPercent}%\n**Heap Used:** ${formatBytes( - memUsage.heapUsed - )}\n**Heap Total:** ${formatBytes(memUsage.heapTotal)}`, - inline: true, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.tag} • Bot ID: ${client.user.id}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const statsEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("📊 NovaBot Performance Dashboard") + .setDescription("Real-time bot statistics and performance metrics") + .setThumbnail(client.user.displayAvatarURL({ dynamic: true, size: 256 })) + .addFields( + { + name: "⏱️ Uptime", + value: `\`${formatUptime(uptime)}\``, + inline: true, + }, + { + name: "🏓 Latency", + value: `\`${client.ws.ping}ms\``, + inline: true, + }, + { + name: "🎯 Commands", + value: `\`${commands}\``, + inline: true, + }, + { + name: "🏰 Servers", + value: `\`${guilds.toLocaleString()}\``, + inline: true, + }, + { + name: "👥 Users", + value: `\`${users.toLocaleString()}\``, + inline: true, + }, + { + name: "📢 Channels", + value: `\`${channels.toLocaleString()}\``, + inline: true, + }, + { + name: "💾 Memory Usage", + value: `\`${formatBytes(usedMem)}\` / \`${formatBytes( + totalMem, + )}\`\n${generateProgressBar(usedMem, totalMem)} ${Math.round( + (usedMem / totalMem) * 100, + )}%`, + inline: false, + }, + { + name: "🖥️ System Info", + value: `**Platform:** ${process.platform}\n**Node.js:** ${ + process.version + }\n**Discord.js:** v${require("discord.js").version}`, + inline: true, + }, + { + name: "⚡ Performance", + value: `**CPU Usage:** ~${cpuPercent}%\n**Heap Used:** ${formatBytes( + memUsage.heapUsed, + )}\n**Heap Total:** ${formatBytes(memUsage.heapTotal)}`, + inline: true, + }, + ) + .setFooter({ + text: `Requested by ${interaction.user.tag} • Bot ID: ${client.user.id}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.reply({ - embeds: [statsEmbed], - flags: 64, // MessageFlags.Ephemeral - }); - }, + await interaction.reply({ + embeds: [statsEmbed], + flags: 64, // MessageFlags.Ephemeral + }); + }, }; diff --git a/commands/general/serverinfo.js b/commands/general/serverinfo.js index eaae502..ac07d7a 100644 --- a/commands/general/serverinfo.js +++ b/commands/general/serverinfo.js @@ -1,123 +1,123 @@ const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { generateProgressBar } = require("@adb/server/utils/helpers"); +const { generateProgressBar } = require("../../utils/helpers"); module.exports = { - data: new SlashCommandBuilder() - .setName("serverinfo") - .setDescription("🏰 Display detailed server information and statistics"), - cooldown: 5, - async execute(interaction, client) { - const guild = interaction.guild; + data: new SlashCommandBuilder() + .setName("serverinfo") + .setDescription("🏰 Display detailed server information and statistics"), + cooldown: 5, + async execute(interaction, client) { + const guild = interaction.guild; - // 🔢 Calculate member statistics - const totalMembers = guild.memberCount; - const botCount = guild.members.cache.filter( - (member) => member.user.bot - ).size; - const humanCount = totalMembers - botCount; + // 🔢 Calculate member statistics + const totalMembers = guild.memberCount; + const botCount = guild.members.cache.filter( + (member) => member.user.bot, + ).size; + const humanCount = totalMembers - botCount; - // 📊 Online member count (approximation) - const onlineMembers = guild.presences.cache.filter( - (presence) => presence.status !== "offline" - ).size; + // 📊 Online member count (approximation) + const onlineMembers = guild.presences.cache.filter( + (presence) => presence.status !== "offline", + ).size; - // 🎭 Verification level mapping - const verificationLevels = { - 0: "None", - 1: "Low", - 2: "Medium", - 3: "High", - 4: "Very High", - }; + // 🎭 Verification level mapping + const verificationLevels = { + 0: "None", + 1: "Low", + 2: "Medium", + 3: "High", + 4: "Very High", + }; - // 🔒 Content filter mapping - const contentFilters = { - 0: "Disabled", - 1: "Members without roles", - 2: "All members", - }; + // 🔒 Content filter mapping + const contentFilters = { + 0: "Disabled", + 1: "Members without roles", + 2: "All members", + }; - const serverEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle(`🏰 ${guild.name}`) - .setDescription(guild.description || "No server description set") - .setThumbnail(guild.iconURL({ dynamic: true, size: 512 })) - .addFields( - { - name: "👑 Owner", - value: `<@${guild.ownerId}>`, - inline: true, - }, - { - name: "🆔 Server ID", - value: `\`${guild.id}\``, - inline: true, - }, - { - name: "📅 Created", - value: ``, - inline: true, - }, - { - name: "👥 Members", - value: `👤 ${humanCount} Humans\n🤖 ${botCount} Bots\n📊 ${totalMembers} Total`, - inline: true, - }, - { - name: "📊 Activity", - value: `🟢 ${onlineMembers} Online\n${generateProgressBar( - onlineMembers, - totalMembers - )} ${Math.round((onlineMembers / totalMembers) * 100)}%`, - inline: true, - }, - { - name: "💎 Boosts", - value: `Level ${guild.premiumTier}\n${ - guild.premiumSubscriptionCount || 0 - } Boosts`, - inline: true, - }, - { - name: "📢 Channels", - value: `💬 ${ - guild.channels.cache.filter((c) => c.type === 0).size - } Text\n🔊 ${ - guild.channels.cache.filter((c) => c.type === 2).size - } Voice\n📁 ${ - guild.channels.cache.filter((c) => c.type === 4).size - } Categories`, - inline: true, - }, - { - name: "🎭 Roles", - value: `${guild.roles.cache.size} Roles`, - inline: true, - }, - { - name: "😀 Emojis", - value: `${guild.emojis.cache.size} Emojis`, - inline: true, - }, - { - name: "🔒 Security", - value: `Verification: ${ - verificationLevels[guild.verificationLevel] - }\nContent Filter: ${contentFilters[guild.explicitContentFilter]}`, - inline: false, - } - ) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + const serverEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle(`🏰 ${guild.name}`) + .setDescription(guild.description || "No server description set") + .setThumbnail(guild.iconURL({ dynamic: true, size: 512 })) + .addFields( + { + name: "👑 Owner", + value: `<@${guild.ownerId}>`, + inline: true, + }, + { + name: "🆔 Server ID", + value: `\`${guild.id}\``, + inline: true, + }, + { + name: "📅 Created", + value: ``, + inline: true, + }, + { + name: "👥 Members", + value: `👤 ${humanCount} Humans\n🤖 ${botCount} Bots\n📊 ${totalMembers} Total`, + inline: true, + }, + { + name: "📊 Activity", + value: `🟢 ${onlineMembers} Online\n${generateProgressBar( + onlineMembers, + totalMembers, + )} ${Math.round((onlineMembers / totalMembers) * 100)}%`, + inline: true, + }, + { + name: "💎 Boosts", + value: `Level ${guild.premiumTier}\n${ + guild.premiumSubscriptionCount || 0 + } Boosts`, + inline: true, + }, + { + name: "📢 Channels", + value: `💬 ${ + guild.channels.cache.filter((c) => c.type === 0).size + } Text\n🔊 ${ + guild.channels.cache.filter((c) => c.type === 2).size + } Voice\n📁 ${ + guild.channels.cache.filter((c) => c.type === 4).size + } Categories`, + inline: true, + }, + { + name: "🎭 Roles", + value: `${guild.roles.cache.size} Roles`, + inline: true, + }, + { + name: "😀 Emojis", + value: `${guild.emojis.cache.size} Emojis`, + inline: true, + }, + { + name: "🔒 Security", + value: `Verification: ${ + verificationLevels[guild.verificationLevel] + }\nContent Filter: ${contentFilters[guild.explicitContentFilter]}`, + inline: false, + }, + ) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - // 🖼️ Add server banner if available - if (guild.bannerURL()) { - serverEmbed.setImage(guild.bannerURL({ dynamic: true, size: 1024 })); - } + // 🖼️ Add server banner if available + if (guild.bannerURL()) { + serverEmbed.setImage(guild.bannerURL({ dynamic: true, size: 1024 })); + } - await interaction.reply({ embeds: [serverEmbed] }); - }, + await interaction.reply({ embeds: [serverEmbed] }); + }, }; diff --git a/commands/moderation/ban.js b/commands/moderation/ban.js index 6e83667..aada2fd 100644 --- a/commands/moderation/ban.js +++ b/commands/moderation/ban.js @@ -1,294 +1,297 @@ const { - SlashCommandBuilder, - EmbedBuilder, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, } = require("discord.js"); -const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); +const { isModeratorOrOwner } = require("../../utils/moderation"); module.exports = { - data: new SlashCommandBuilder() - .setName("ban") - .setDescription("🔨 Ban a user from the server with optional reason") - .addUserOption((option) => - option.setName("user").setDescription("The user to ban").setRequired(true) - ) - .addStringOption((option) => - option - .setName("reason") - .setDescription("Reason for the ban") - .setRequired(false) - ) - .addIntegerOption((option) => - option - .setName("delete_days") - .setDescription("Days of messages to delete (0-7)") - .setRequired(false) - .setMinValue(0) - .setMaxValue(7) - ) - .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), - cooldown: 5, - async execute(interaction, client) { - // 🛡️ Enhanced moderator check - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noModPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Moderator Access Required") - .setDescription( - "This command is restricted to server moderators and administrators only." - ) - .addFields({ - name: "🔐 Required Permissions", - value: - "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Ban Members permission\n• A moderator role", - inline: false, - }) - .setFooter({ - text: "Contact a server administrator if you believe this is an error.", - }); + data: new SlashCommandBuilder() + .setName("ban") + .setDescription("🔨 Ban a user from the server with optional reason") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to ban") + .setRequired(true), + ) + .addStringOption((option) => + option + .setName("reason") + .setDescription("Reason for the ban") + .setRequired(false), + ) + .addIntegerOption((option) => + option + .setName("delete_days") + .setDescription("Days of messages to delete (0-7)") + .setRequired(false) + .setMinValue(0) + .setMaxValue(7), + ) + .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), + cooldown: 5, + async execute(interaction, client) { + // 🛡️ Enhanced moderator check + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noModPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Moderator Access Required") + .setDescription( + "This command is restricted to server moderators and administrators only.", + ) + .addFields({ + name: "🔐 Required Permissions", + value: + "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Ban Members permission\n• A moderator role", + inline: false, + }) + .setFooter({ + text: "Contact a server administrator if you believe this is an error.", + }); - return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); + } - const targetUser = interaction.options.getUser("user"); - const reason = - interaction.options.getString("reason") || "No reason provided"; - const deleteDays = interaction.options.getInteger("delete_days") || 0; - const targetMember = interaction.guild.members.cache.get(targetUser.id); + const targetUser = interaction.options.getUser("user"); + const reason = + interaction.options.getString("reason") || "No reason provided"; + const deleteDays = interaction.options.getInteger("delete_days") || 0; + const targetMember = interaction.guild.members.cache.get(targetUser.id); - // 🛡️ Permission checks - if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Permission Denied") - .setDescription( - "You need the `Ban Members` permission to use this command." - ) - .setFooter({ - text: "Contact an administrator if you believe this is an error.", - }); + // 🛡️ Permission checks + if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Permission Denied") + .setDescription( + "You need the `Ban Members` permission to use this command.", + ) + .setFooter({ + text: "Contact an administrator if you believe this is an error.", + }); - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } - if ( - !interaction.guild.members.me.permissions.has( - PermissionFlagsBits.BanMembers - ) - ) { - const botNoPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Bot Permission Missing") - .setDescription( - "I need the `Ban Members` permission to execute this command." - ) - .setFooter({ - text: "Please contact an administrator to grant the required permissions.", - }); + if ( + !interaction.guild.members.me.permissions.has( + PermissionFlagsBits.BanMembers, + ) + ) { + const botNoPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Bot Permission Missing") + .setDescription( + "I need the `Ban Members` permission to execute this command.", + ) + .setFooter({ + text: "Please contact an administrator to grant the required permissions.", + }); - return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); + } - // 🚫 Self-ban protection - if (targetUser.id === interaction.user.id) { - const selfBanEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("🤔 Hold Up!") - .setDescription( - "You can't ban yourself! If you want to leave, use the leave server option." - ) - .setFooter({ text: "Self-destruction is not the answer! 😄" }); + // 🚫 Self-ban protection + if (targetUser.id === interaction.user.id) { + const selfBanEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("🤔 Hold Up!") + .setDescription( + "You can't ban yourself! If you want to leave, use the leave server option.", + ) + .setFooter({ text: "Self-destruction is not the answer! 😄" }); - return interaction.reply({ embeds: [selfBanEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [selfBanEmbed], ephemeral: true }); + } - // 🤖 Bot protection - if (targetUser.id === client.user.id) { - const botBanEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("💔 Betrayal!") - .setDescription( - "I can't ban myself! After all we've been through together..." - ) - .setFooter({ text: "Et tu, Brute? 😭" }); + // 🤖 Bot protection + if (targetUser.id === client.user.id) { + const botBanEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("💔 Betrayal!") + .setDescription( + "I can't ban myself! After all we've been through together...", + ) + .setFooter({ text: "Et tu, Brute? 😭" }); - return interaction.reply({ embeds: [botBanEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [botBanEmbed], ephemeral: true }); + } - // 🏆 Role hierarchy check (only if user is in server) - if (targetMember) { - const executorHighestRole = interaction.member.roles.highest; - const targetHighestRole = targetMember.roles.highest; - const botHighestRole = interaction.guild.members.me.roles.highest; + // 🏆 Role hierarchy check (only if user is in server) + if (targetMember) { + const executorHighestRole = interaction.member.roles.highest; + const targetHighestRole = targetMember.roles.highest; + const botHighestRole = interaction.guild.members.me.roles.highest; - if (targetHighestRole.position >= executorHighestRole.position) { - const hierarchyEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("⚡ Role Hierarchy Error") - .setDescription( - "You cannot ban someone with a role equal to or higher than yours." - ) - .addFields({ - name: "🏆 Role Comparison", - value: `Your highest role: **${executorHighestRole.name}** (Position: ${executorHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, - inline: false, - }) - .setFooter({ text: "Role hierarchy prevents this action." }); + if (targetHighestRole.position >= executorHighestRole.position) { + const hierarchyEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("⚡ Role Hierarchy Error") + .setDescription( + "You cannot ban someone with a role equal to or higher than yours.", + ) + .addFields({ + name: "🏆 Role Comparison", + value: `Your highest role: **${executorHighestRole.name}** (Position: ${executorHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, + inline: false, + }) + .setFooter({ text: "Role hierarchy prevents this action." }); - return interaction.reply({ embeds: [hierarchyEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [hierarchyEmbed], ephemeral: true }); + } - if (targetHighestRole.position >= botHighestRole.position) { - const botHierarchyEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("⚡ Bot Role Hierarchy Error") - .setDescription( - "I cannot ban someone with a role equal to or higher than my highest role." - ) - .addFields({ - name: "🤖 Role Comparison", - value: `My highest role: **${botHighestRole.name}** (Position: ${botHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, - inline: false, - }) - .setFooter({ - text: "Please move my role higher or lower the target's role.", - }); + if (targetHighestRole.position >= botHighestRole.position) { + const botHierarchyEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("⚡ Bot Role Hierarchy Error") + .setDescription( + "I cannot ban someone with a role equal to or higher than my highest role.", + ) + .addFields({ + name: "🤖 Role Comparison", + value: `My highest role: **${botHighestRole.name}** (Position: ${botHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, + inline: false, + }) + .setFooter({ + text: "Please move my role higher or lower the target's role.", + }); - return interaction.reply({ - embeds: [botHierarchyEmbed], - ephemeral: true, - }); - } - } + return interaction.reply({ + embeds: [botHierarchyEmbed], + ephemeral: true, + }); + } + } - // 🔍 Check if user is already banned - try { - const bans = await interaction.guild.bans.fetch(); - if (bans.has(targetUser.id)) { - const alreadyBannedEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("⚠️ Already Banned") - .setDescription( - `**${targetUser.tag}** is already banned from this server.` - ) - .setFooter({ - text: "Use the unban command if you want to remove the ban.", - }); + // 🔍 Check if user is already banned + try { + const bans = await interaction.guild.bans.fetch(); + if (bans.has(targetUser.id)) { + const alreadyBannedEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("⚠️ Already Banned") + .setDescription( + `**${targetUser.tag}** is already banned from this server.`, + ) + .setFooter({ + text: "Use the unban command if you want to remove the ban.", + }); - return interaction.reply({ - embeds: [alreadyBannedEmbed], - ephemeral: true, - }); - } - } catch (error) { - console.error("❌ Error checking ban list:", error); - } + return interaction.reply({ + embeds: [alreadyBannedEmbed], + ephemeral: true, + }); + } + } catch (error) { + console.error("❌ Error checking ban list:", error); + } - // 🔨 Execute the ban - try { - // 📬 Try to DM the user before banning (only if they're in the server) - if (targetMember) { - try { - const dmEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle(`🔨 You've been banned from ${interaction.guild.name}`) - .setDescription( - `You have been permanently removed from **${interaction.guild.name}**.` - ) - .addFields( - { name: "📝 Reason", value: reason, inline: false }, - { - name: "👤 Banned by", - value: interaction.user.tag, - inline: true, - }, - { - name: "📅 Date", - value: ``, - inline: true, - }, - { - name: "🗑️ Messages Deleted", - value: `${deleteDays} day${deleteDays === 1 ? "" : "s"}`, - inline: true, - } - ) - .setFooter({ - text: "This ban is permanent unless manually removed by staff.", - }) - .setTimestamp(); + // 🔨 Execute the ban + try { + // 📬 Try to DM the user before banning (only if they're in the server) + if (targetMember) { + try { + const dmEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle(`🔨 You've been banned from ${interaction.guild.name}`) + .setDescription( + `You have been permanently removed from **${interaction.guild.name}**.`, + ) + .addFields( + { name: "📝 Reason", value: reason, inline: false }, + { + name: "👤 Banned by", + value: interaction.user.tag, + inline: true, + }, + { + name: "📅 Date", + value: ``, + inline: true, + }, + { + name: "🗑️ Messages Deleted", + value: `${deleteDays} day${deleteDays === 1 ? "" : "s"}`, + inline: true, + }, + ) + .setFooter({ + text: "This ban is permanent unless manually removed by staff.", + }) + .setTimestamp(); - await targetUser.send({ embeds: [dmEmbed] }); - } catch (dmError) { - console.log(`📬 Could not DM ${targetUser.tag} about their ban.`); - } - } + await targetUser.send({ embeds: [dmEmbed] }); + } catch (dmError) { + console.log(`📬 Could not DM ${targetUser.tag} about their ban.`); + } + } - // 🔨 Perform the ban - await interaction.guild.members.ban(targetUser.id, { - reason: `${reason} | Banned by: ${interaction.user.tag}`, - deleteMessageDays: deleteDays, - }); + // 🔨 Perform the ban + await interaction.guild.members.ban(targetUser.id, { + reason: `${reason} | Banned by: ${interaction.user.tag}`, + deleteMessageDays: deleteDays, + }); - // ✅ Success confirmation - const successEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🔨 User Banned Successfully") - .setDescription( - `**${targetUser.tag}** has been banned from the server.` - ) - .addFields( - { - name: "👤 Banned User", - value: `${targetUser.tag} (${targetUser.id})`, - inline: true, - }, - { - name: "👮 Moderator", - value: `${interaction.user.tag}`, - inline: true, - }, - { name: "📝 Reason", value: reason, inline: false }, - { - name: "🗑️ Messages Deleted", - value: `${deleteDays} day${deleteDays === 1 ? "" : "s"} worth`, - inline: true, - }, - { - name: "📊 Status", - value: targetMember ? "🟢 Was in server" : "🔴 Not in server", - inline: true, - } - ) - .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) - .setFooter({ - text: `Action performed by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + // ✅ Success confirmation + const successEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🔨 User Banned Successfully") + .setDescription( + `**${targetUser.tag}** has been banned from the server.`, + ) + .addFields( + { + name: "👤 Banned User", + value: `${targetUser.tag} (${targetUser.id})`, + inline: true, + }, + { + name: "👮 Moderator", + value: `${interaction.user.tag}`, + inline: true, + }, + { name: "📝 Reason", value: reason, inline: false }, + { + name: "🗑️ Messages Deleted", + value: `${deleteDays} day${deleteDays === 1 ? "" : "s"} worth`, + inline: true, + }, + { + name: "📊 Status", + value: targetMember ? "🟢 Was in server" : "🔴 Not in server", + inline: true, + }, + ) + .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) + .setFooter({ + text: `Action performed by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - await interaction.reply({ embeds: [successEmbed] }); + await interaction.reply({ embeds: [successEmbed] }); - // 📊 Log the action - console.log( - `🔨 ${targetUser.tag} was banned from ${interaction.guild.name} by ${interaction.user.tag}. Reason: ${reason}` - ); - } catch (error) { - console.error("❌ Error banning user:", error); + // 📊 Log the action + console.log( + `🔨 ${targetUser.tag} was banned from ${interaction.guild.name} by ${interaction.user.tag}. Reason: ${reason}`, + ); + } catch (error) { + console.error("❌ Error banning user:", error); - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Ban Failed") - .setDescription("An error occurred while trying to ban the user.") - .addFields({ - name: "🔧 Possible Issues", - value: - "• Missing permissions\n• Role hierarchy conflicts\n• User ID not found\n• Bot malfunction", - inline: false, - }) - .setFooter({ text: "Please try again or contact an administrator." }); + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Ban Failed") + .setDescription("An error occurred while trying to ban the user.") + .addFields({ + name: "🔧 Possible Issues", + value: + "• Missing permissions\n• Role hierarchy conflicts\n• User ID not found\n• Bot malfunction", + inline: false, + }) + .setFooter({ text: "Please try again or contact an administrator." }); - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - }, + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); + } + }, }; diff --git a/commands/moderation/kick.js b/commands/moderation/kick.js index 216faff..277d571 100644 --- a/commands/moderation/kick.js +++ b/commands/moderation/kick.js @@ -1,248 +1,248 @@ const { - SlashCommandBuilder, - EmbedBuilder, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, } = require("discord.js"); -const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); +const { isModeratorOrOwner } = require("../../utils/moderation"); module.exports = { - data: new SlashCommandBuilder() - .setName("kick") - .setDescription("👢 Kick a user from the server with optional reason") - .addUserOption((option) => - option - .setName("user") - .setDescription("The user to kick") - .setRequired(true) - ) - .addStringOption((option) => - option - .setName("reason") - .setDescription("Reason for the kick") - .setRequired(false) - ) - .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), - cooldown: 5, - async execute(interaction, client) { - // 🛡️ Enhanced moderator check - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noModPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Moderator Access Required") - .setDescription( - "This command is restricted to server moderators and administrators only." - ) - .addFields({ - name: "🔐 Required Permissions", - value: - "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Kick Members permission\n• A moderator role", - inline: false, - }) - .setFooter({ - text: "Contact a server administrator if you believe this is an error.", - }); - - return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); - } - - const targetUser = interaction.options.getUser("user"); - const reason = - interaction.options.getString("reason") || "No reason provided"; - const targetMember = interaction.guild.members.cache.get(targetUser.id); - - // 🛡️ Permission checks - if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Permission Denied") - .setDescription( - "You need the `Kick Members` permission to use this command." - ) - .setFooter({ - text: "Contact an administrator if you believe this is an error.", - }); - - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } - - if ( - !interaction.guild.members.me.permissions.has( - PermissionFlagsBits.KickMembers - ) - ) { - const botNoPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Bot Permission Missing") - .setDescription( - "I need the `Kick Members` permission to execute this command." - ) - .setFooter({ - text: "Please contact an administrator to grant the required permissions.", - }); - - return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); - } - - // 🎯 Target validation - if (!targetMember) { - const notFoundEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("⚠️ User Not Found") - .setDescription("This user is not a member of this server.") - .setFooter({ text: "They might have already left the server." }); - - return interaction.reply({ embeds: [notFoundEmbed], ephemeral: true }); - } - - // 🚫 Self-kick protection - if (targetUser.id === interaction.user.id) { - const selfKickEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("🤔 Hold Up!") - .setDescription( - "You can't kick yourself! If you want to leave, use the leave server option." - ) - .setFooter({ text: "Nice try though! 😄" }); - - return interaction.reply({ embeds: [selfKickEmbed], ephemeral: true }); - } - - // 🤖 Bot protection - if (targetUser.id === client.user.id) { - const botKickEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("😢 You Want to Kick Me?") - .setDescription( - "I can't kick myself! If you really want me gone, you'll have to do it manually." - ) - .setFooter({ text: "But I thought we were friends... 💔" }); - - return interaction.reply({ embeds: [botKickEmbed], ephemeral: true }); - } - - // 🏆 Role hierarchy check - const executorHighestRole = interaction.member.roles.highest; - const targetHighestRole = targetMember.roles.highest; - const botHighestRole = interaction.guild.members.me.roles.highest; - - if (targetHighestRole.position >= executorHighestRole.position) { - const hierarchyEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("⚡ Role Hierarchy Error") - .setDescription( - "You cannot kick someone with a role equal to or higher than yours." - ) - .addFields({ - name: "🏆 Role Comparison", - value: `Your highest role: **${executorHighestRole.name}** (Position: ${executorHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, - inline: false, - }) - .setFooter({ text: "Role hierarchy prevents this action." }); - - return interaction.reply({ embeds: [hierarchyEmbed], ephemeral: true }); - } - - if (targetHighestRole.position >= botHighestRole.position) { - const botHierarchyEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("⚡ Bot Role Hierarchy Error") - .setDescription( - "I cannot kick someone with a role equal to or higher than my highest role." - ) - .addFields({ - name: "🤖 Role Comparison", - value: `My highest role: **${botHighestRole.name}** (Position: ${botHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, - inline: false, - }) - .setFooter({ - text: "Please move my role higher or lower the target's role.", - }); - - return interaction.reply({ - embeds: [botHierarchyEmbed], - ephemeral: true, - }); - } - - // 👢 Execute the kick - try { - // 📬 Try to DM the user before kicking - try { - const dmEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle(`👢 You've been kicked from ${interaction.guild.name}`) - .setDescription( - `You have been removed from **${interaction.guild.name}**.` - ) - .addFields( - { name: "📝 Reason", value: reason, inline: false }, - { name: "👤 Kicked by", value: interaction.user.tag, inline: true }, - { - name: "📅 Date", - value: ``, - inline: true, - } - ) - .setFooter({ text: "You can rejoin if you have an invite link." }) - .setTimestamp(); - - await targetUser.send({ embeds: [dmEmbed] }); - } catch (dmError) { - console.log(`📬 Could not DM ${targetUser.tag} about their kick.`); - } - - // 👢 Perform the kick - await targetMember.kick(reason); - - // ✅ Success confirmation - const successEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ User Kicked Successfully") - .setDescription( - `**${targetUser.tag}** has been kicked from the server.` - ) - .addFields( - { - name: "👤 Kicked User", - value: `${targetUser.tag} (${targetUser.id})`, - inline: true, - }, - { - name: "👮 Moderator", - value: `${interaction.user.tag}`, - inline: true, - }, - { name: "📝 Reason", value: reason, inline: false } - ) - .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) - .setFooter({ - text: `Action performed by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [successEmbed] }); - - // 📊 Log the action - console.log( - `👢 ${targetUser.tag} was kicked from ${interaction.guild.name} by ${interaction.user.tag}. Reason: ${reason}` - ); - } catch (error) { - console.error("❌ Error kicking user:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Kick Failed") - .setDescription("An error occurred while trying to kick the user.") - .addFields({ - name: "🔧 Possible Issues", - value: - "• Missing permissions\n• Role hierarchy conflicts\n• User already left\n• Bot malfunction", - inline: false, - }) - .setFooter({ text: "Please try again or contact an administrator." }); - - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - }, + data: new SlashCommandBuilder() + .setName("kick") + .setDescription("👢 Kick a user from the server with optional reason") + .addUserOption((option) => + option + .setName("user") + .setDescription("The user to kick") + .setRequired(true), + ) + .addStringOption((option) => + option + .setName("reason") + .setDescription("Reason for the kick") + .setRequired(false), + ) + .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), + cooldown: 5, + async execute(interaction, client) { + // 🛡️ Enhanced moderator check + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noModPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Moderator Access Required") + .setDescription( + "This command is restricted to server moderators and administrators only.", + ) + .addFields({ + name: "🔐 Required Permissions", + value: + "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Kick Members permission\n• A moderator role", + inline: false, + }) + .setFooter({ + text: "Contact a server administrator if you believe this is an error.", + }); + + return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); + } + + const targetUser = interaction.options.getUser("user"); + const reason = + interaction.options.getString("reason") || "No reason provided"; + const targetMember = interaction.guild.members.cache.get(targetUser.id); + + // 🛡️ Permission checks + if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Permission Denied") + .setDescription( + "You need the `Kick Members` permission to use this command.", + ) + .setFooter({ + text: "Contact an administrator if you believe this is an error.", + }); + + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } + + if ( + !interaction.guild.members.me.permissions.has( + PermissionFlagsBits.KickMembers, + ) + ) { + const botNoPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Bot Permission Missing") + .setDescription( + "I need the `Kick Members` permission to execute this command.", + ) + .setFooter({ + text: "Please contact an administrator to grant the required permissions.", + }); + + return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); + } + + // 🎯 Target validation + if (!targetMember) { + const notFoundEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("⚠️ User Not Found") + .setDescription("This user is not a member of this server.") + .setFooter({ text: "They might have already left the server." }); + + return interaction.reply({ embeds: [notFoundEmbed], ephemeral: true }); + } + + // 🚫 Self-kick protection + if (targetUser.id === interaction.user.id) { + const selfKickEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("🤔 Hold Up!") + .setDescription( + "You can't kick yourself! If you want to leave, use the leave server option.", + ) + .setFooter({ text: "Nice try though! 😄" }); + + return interaction.reply({ embeds: [selfKickEmbed], ephemeral: true }); + } + + // 🤖 Bot protection + if (targetUser.id === client.user.id) { + const botKickEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("😢 You Want to Kick Me?") + .setDescription( + "I can't kick myself! If you really want me gone, you'll have to do it manually.", + ) + .setFooter({ text: "But I thought we were friends... 💔" }); + + return interaction.reply({ embeds: [botKickEmbed], ephemeral: true }); + } + + // 🏆 Role hierarchy check + const executorHighestRole = interaction.member.roles.highest; + const targetHighestRole = targetMember.roles.highest; + const botHighestRole = interaction.guild.members.me.roles.highest; + + if (targetHighestRole.position >= executorHighestRole.position) { + const hierarchyEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("⚡ Role Hierarchy Error") + .setDescription( + "You cannot kick someone with a role equal to or higher than yours.", + ) + .addFields({ + name: "🏆 Role Comparison", + value: `Your highest role: **${executorHighestRole.name}** (Position: ${executorHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, + inline: false, + }) + .setFooter({ text: "Role hierarchy prevents this action." }); + + return interaction.reply({ embeds: [hierarchyEmbed], ephemeral: true }); + } + + if (targetHighestRole.position >= botHighestRole.position) { + const botHierarchyEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("⚡ Bot Role Hierarchy Error") + .setDescription( + "I cannot kick someone with a role equal to or higher than my highest role.", + ) + .addFields({ + name: "🤖 Role Comparison", + value: `My highest role: **${botHighestRole.name}** (Position: ${botHighestRole.position})\nTarget's highest role: **${targetHighestRole.name}** (Position: ${targetHighestRole.position})`, + inline: false, + }) + .setFooter({ + text: "Please move my role higher or lower the target's role.", + }); + + return interaction.reply({ + embeds: [botHierarchyEmbed], + ephemeral: true, + }); + } + + // 👢 Execute the kick + try { + // 📬 Try to DM the user before kicking + try { + const dmEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle(`👢 You've been kicked from ${interaction.guild.name}`) + .setDescription( + `You have been removed from **${interaction.guild.name}**.`, + ) + .addFields( + { name: "📝 Reason", value: reason, inline: false }, + { name: "👤 Kicked by", value: interaction.user.tag, inline: true }, + { + name: "📅 Date", + value: ``, + inline: true, + }, + ) + .setFooter({ text: "You can rejoin if you have an invite link." }) + .setTimestamp(); + + await targetUser.send({ embeds: [dmEmbed] }); + } catch (dmError) { + console.log(`📬 Could not DM ${targetUser.tag} about their kick.`); + } + + // 👢 Perform the kick + await targetMember.kick(reason); + + // ✅ Success confirmation + const successEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ User Kicked Successfully") + .setDescription( + `**${targetUser.tag}** has been kicked from the server.`, + ) + .addFields( + { + name: "👤 Kicked User", + value: `${targetUser.tag} (${targetUser.id})`, + inline: true, + }, + { + name: "👮 Moderator", + value: `${interaction.user.tag}`, + inline: true, + }, + { name: "📝 Reason", value: reason, inline: false }, + ) + .setThumbnail(targetUser.displayAvatarURL({ dynamic: true })) + .setFooter({ + text: `Action performed by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [successEmbed] }); + + // 📊 Log the action + console.log( + `👢 ${targetUser.tag} was kicked from ${interaction.guild.name} by ${interaction.user.tag}. Reason: ${reason}`, + ); + } catch (error) { + console.error("❌ Error kicking user:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Kick Failed") + .setDescription("An error occurred while trying to kick the user.") + .addFields({ + name: "🔧 Possible Issues", + value: + "• Missing permissions\n• Role hierarchy conflicts\n• User already left\n• Bot malfunction", + inline: false, + }) + .setFooter({ text: "Please try again or contact an administrator." }); + + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); + } + }, }; diff --git a/commands/moderation/purge.js b/commands/moderation/purge.js index 642b2be..882dfd5 100644 --- a/commands/moderation/purge.js +++ b/commands/moderation/purge.js @@ -1,240 +1,240 @@ const { - SlashCommandBuilder, - EmbedBuilder, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + PermissionFlagsBits, } = require("discord.js"); -const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); +const { isModeratorOrOwner } = require("../../utils/moderation"); module.exports = { - data: new SlashCommandBuilder() - .setName("purge") - .setDescription("🗑️ Bulk delete messages from the current channel") - .addIntegerOption((option) => - option - .setName("amount") - .setDescription("Number of messages to delete (1-100)") - .setRequired(true) - .setMinValue(1) - .setMaxValue(100) - ) - .addUserOption((option) => - option - .setName("user") - .setDescription("Only delete messages from this user") - .setRequired(false) - ) - .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), - cooldown: 5, - async execute(interaction, client) { - // 🛡️ Enhanced moderator check - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noModPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Moderator Access Required") - .setDescription( - "This command is restricted to server moderators and administrators only." - ) - .addFields({ - name: "🔐 Required Permissions", - value: - "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Manage Messages permission\n• A moderator role", - inline: false, - }) - .setFooter({ - text: "Contact a server administrator if you believe this is an error.", - }); - - return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); - } - - const amount = interaction.options.getInteger("amount"); - const targetUser = interaction.options.getUser("user"); - - // 🛡️ Permission checks - if ( - !interaction.member.permissions.has(PermissionFlagsBits.ManageMessages) - ) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Permission Denied") - .setDescription( - "You need the `Manage Messages` permission to use this command." - ) - .setFooter({ - text: "Contact an administrator if you believe this is an error.", - }); - - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } - - if ( - !interaction.guild.members.me.permissions.has( - PermissionFlagsBits.ManageMessages - ) - ) { - const botNoPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Bot Permission Missing") - .setDescription( - "I need the `Manage Messages` permission to execute this command." - ) - .setFooter({ - text: "Please contact an administrator to grant the required permissions.", - }); - - return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); - } - - // ⏳ Defer reply for processing time - await interaction.deferReply({ ephemeral: true }); - - try { - // 📥 Fetch messages - let messages; - - if (targetUser) { - // 🎯 Fetch more messages to filter by user - const fetchedMessages = await interaction.channel.messages.fetch({ - limit: 100, - }); - messages = fetchedMessages - .filter((msg) => msg.author.id === targetUser.id) - .first(amount); - } else { - // 📋 Fetch specified amount - messages = await interaction.channel.messages.fetch({ limit: amount }); - } - - if (messages.size === 0) { - const noMessagesEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("⚠️ No Messages Found") - .setDescription( - targetUser - ? `No recent messages from ${targetUser.tag} found.` - : "No messages found to delete." - ) - .setFooter({ - text: "Try a different user or check if there are messages in this channel.", - }); - - return interaction.editReply({ embeds: [noMessagesEmbed] }); - } - - // ⏰ Filter out messages older than 14 days (Discord limitation) - const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000; - const deletableMessages = messages.filter( - (msg) => msg.createdTimestamp > twoWeeksAgo - ); - const oldMessages = messages.size - deletableMessages.size; - - if (deletableMessages.size === 0) { - const tooOldEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("⚠️ Messages Too Old") - .setDescription( - "All found messages are older than 14 days and cannot be bulk deleted." - ) - .addFields({ - name: "📅 Discord Limitation", - value: "Messages older than 14 days must be deleted individually.", - inline: false, - }) - .setFooter({ - text: "This is a Discord API limitation, not a bot issue.", - }); - - return interaction.editReply({ embeds: [tooOldEmbed] }); - } - - // 🗑️ Perform bulk delete - const deletedMessages = await interaction.channel.bulkDelete( - deletableMessages, - true - ); - - // 📊 Success confirmation - const successEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("🗑️ Messages Deleted Successfully") - .setDescription( - `Successfully deleted **${deletedMessages.size}** message${ - deletedMessages.size === 1 ? "" : "s" - }.` - ) - .addFields( - { - name: "📊 Details", - value: targetUser - ? `🎯 **Target:** ${targetUser.tag}\n📝 **Deleted:** ${deletedMessages.size} messages\n🔍 **Searched:** 100 messages` - : `📝 **Deleted:** ${deletedMessages.size} messages\n📋 **Requested:** ${amount} messages`, - inline: false, - }, - { - name: "👮 Moderator", - value: `${interaction.user.tag}`, - inline: true, - }, - { - name: "📍 Channel", - value: `${interaction.channel}`, - inline: true, - } - ); - - // ⚠️ Add warning about old messages if any - if (oldMessages > 0) { - successEmbed.addFields({ - name: "⚠️ Notice", - value: `${oldMessages} message${ - oldMessages === 1 ? " was" : "s were" - } older than 14 days and could not be deleted.`, - inline: false, - }); - } - - successEmbed - .setFooter({ - text: `Action performed by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - await interaction.editReply({ embeds: [successEmbed] }); - - // 📊 Log the action - console.log( - `🗑️ ${deletedMessages.size} messages deleted from ${ - interaction.channel.name - } in ${interaction.guild.name} by ${interaction.user.tag}${ - targetUser ? ` (target: ${targetUser.tag})` : "" - }` - ); - - // 🎉 Auto-delete confirmation after 10 seconds - setTimeout(async () => { - try { - await interaction.deleteReply(); - } catch (error) { - // Ignore errors when deleting (message might already be gone) - } - }, 10000); - } catch (error) { - console.error("❌ Error during purge:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Purge Failed") - .setDescription("An error occurred while trying to delete messages.") - .addFields({ - name: "🔧 Possible Issues", - value: - "• Missing permissions\n• Messages too old (>14 days)\n• Channel restrictions\n• Bot malfunction", - inline: false, - }) - .setFooter({ text: "Please try again or contact an administrator." }); - - await interaction.editReply({ embeds: [errorEmbed] }); - } - }, + data: new SlashCommandBuilder() + .setName("purge") + .setDescription("🗑️ Bulk delete messages from the current channel") + .addIntegerOption((option) => + option + .setName("amount") + .setDescription("Number of messages to delete (1-100)") + .setRequired(true) + .setMinValue(1) + .setMaxValue(100), + ) + .addUserOption((option) => + option + .setName("user") + .setDescription("Only delete messages from this user") + .setRequired(false), + ) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), + cooldown: 5, + async execute(interaction, client) { + // 🛡️ Enhanced moderator check + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noModPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Moderator Access Required") + .setDescription( + "This command is restricted to server moderators and administrators only.", + ) + .addFields({ + name: "🔐 Required Permissions", + value: + "You need one of the following:\n• Administrator permission\n• Moderate Members permission\n• Manage Messages permission\n• A moderator role", + inline: false, + }) + .setFooter({ + text: "Contact a server administrator if you believe this is an error.", + }); + + return interaction.reply({ embeds: [noModPermEmbed], ephemeral: true }); + } + + const amount = interaction.options.getInteger("amount"); + const targetUser = interaction.options.getUser("user"); + + // 🛡️ Permission checks + if ( + !interaction.member.permissions.has(PermissionFlagsBits.ManageMessages) + ) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Permission Denied") + .setDescription( + "You need the `Manage Messages` permission to use this command.", + ) + .setFooter({ + text: "Contact an administrator if you believe this is an error.", + }); + + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } + + if ( + !interaction.guild.members.me.permissions.has( + PermissionFlagsBits.ManageMessages, + ) + ) { + const botNoPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Bot Permission Missing") + .setDescription( + "I need the `Manage Messages` permission to execute this command.", + ) + .setFooter({ + text: "Please contact an administrator to grant the required permissions.", + }); + + return interaction.reply({ embeds: [botNoPermEmbed], ephemeral: true }); + } + + // ⏳ Defer reply for processing time + await interaction.deferReply({ ephemeral: true }); + + try { + // 📥 Fetch messages + let messages; + + if (targetUser) { + // 🎯 Fetch more messages to filter by user + const fetchedMessages = await interaction.channel.messages.fetch({ + limit: 100, + }); + messages = fetchedMessages + .filter((msg) => msg.author.id === targetUser.id) + .first(amount); + } else { + // 📋 Fetch specified amount + messages = await interaction.channel.messages.fetch({ limit: amount }); + } + + if (messages.size === 0) { + const noMessagesEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("⚠️ No Messages Found") + .setDescription( + targetUser + ? `No recent messages from ${targetUser.tag} found.` + : "No messages found to delete.", + ) + .setFooter({ + text: "Try a different user or check if there are messages in this channel.", + }); + + return interaction.editReply({ embeds: [noMessagesEmbed] }); + } + + // ⏰ Filter out messages older than 14 days (Discord limitation) + const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000; + const deletableMessages = messages.filter( + (msg) => msg.createdTimestamp > twoWeeksAgo, + ); + const oldMessages = messages.size - deletableMessages.size; + + if (deletableMessages.size === 0) { + const tooOldEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("⚠️ Messages Too Old") + .setDescription( + "All found messages are older than 14 days and cannot be bulk deleted.", + ) + .addFields({ + name: "📅 Discord Limitation", + value: "Messages older than 14 days must be deleted individually.", + inline: false, + }) + .setFooter({ + text: "This is a Discord API limitation, not a bot issue.", + }); + + return interaction.editReply({ embeds: [tooOldEmbed] }); + } + + // 🗑️ Perform bulk delete + const deletedMessages = await interaction.channel.bulkDelete( + deletableMessages, + true, + ); + + // 📊 Success confirmation + const successEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("🗑️ Messages Deleted Successfully") + .setDescription( + `Successfully deleted **${deletedMessages.size}** message${ + deletedMessages.size === 1 ? "" : "s" + }.`, + ) + .addFields( + { + name: "📊 Details", + value: targetUser + ? `🎯 **Target:** ${targetUser.tag}\n📝 **Deleted:** ${deletedMessages.size} messages\n🔍 **Searched:** 100 messages` + : `📝 **Deleted:** ${deletedMessages.size} messages\n📋 **Requested:** ${amount} messages`, + inline: false, + }, + { + name: "👮 Moderator", + value: `${interaction.user.tag}`, + inline: true, + }, + { + name: "📍 Channel", + value: `${interaction.channel}`, + inline: true, + }, + ); + + // ⚠️ Add warning about old messages if any + if (oldMessages > 0) { + successEmbed.addFields({ + name: "⚠️ Notice", + value: `${oldMessages} message${ + oldMessages === 1 ? " was" : "s were" + } older than 14 days and could not be deleted.`, + inline: false, + }); + } + + successEmbed + .setFooter({ + text: `Action performed by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await interaction.editReply({ embeds: [successEmbed] }); + + // 📊 Log the action + console.log( + `🗑️ ${deletedMessages.size} messages deleted from ${ + interaction.channel.name + } in ${interaction.guild.name} by ${interaction.user.tag}${ + targetUser ? ` (target: ${targetUser.tag})` : "" + }`, + ); + + // 🎉 Auto-delete confirmation after 10 seconds + setTimeout(async () => { + try { + await interaction.deleteReply(); + } catch (error) { + // Ignore errors when deleting (message might already be gone) + } + }, 10000); + } catch (error) { + console.error("❌ Error during purge:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Purge Failed") + .setDescription("An error occurred while trying to delete messages.") + .addFields({ + name: "🔧 Possible Issues", + value: + "• Missing permissions\n• Messages too old (>14 days)\n• Channel restrictions\n• Bot malfunction", + inline: false, + }) + .setFooter({ text: "Please try again or contact an administrator." }); + + await interaction.editReply({ embeds: [errorEmbed] }); + } + }, }; diff --git a/commands/moderation/ticket.js b/commands/moderation/ticket.js index 8fe0cf3..ecb177b 100644 --- a/commands/moderation/ticket.js +++ b/commands/moderation/ticket.js @@ -1,319 +1,319 @@ const { - SlashCommandBuilder, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ChannelType, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ChannelType, + PermissionFlagsBits, } = require("discord.js"); const Database = require("../../utils/database"); const { - isModeratorOrOwner, - generateTicketId, - getPriorityColor, - formatTicketStatus, - timeAgo, -} = require("@adb/server/utils/moderation"); + isModeratorOrOwner, + generateTicketId, + getPriorityColor, + formatTicketStatus, + timeAgo, +} = require("../../utils/moderation"); module.exports = { - data: new SlashCommandBuilder() - .setName("ticket") - .setDescription("🎫 Create a support ticket") - .addStringOption((option) => - option - .setName("title") - .setDescription("Brief title for your ticket") - .setRequired(true) - .setMaxLength(100) - ) - .addStringOption((option) => - option - .setName("description") - .setDescription("Detailed description of your issue") - .setRequired(true) - .setMaxLength(1000) - ) - .addStringOption((option) => - option - .setName("priority") - .setDescription("Priority level of your ticket") - .setRequired(false) - .addChoices( - { name: "🔴 High Priority", value: "high" }, - { name: "🟡 Medium Priority", value: "medium" }, - { name: "🟢 Low Priority", value: "low" } - ) - ) - .addAttachmentOption((option) => - option - .setName("attachment") - .setDescription( - "Upload a file, screenshot, or proof related to your ticket" - ) - .setRequired(false) - ), - cooldown: 60, // 1 minute cooldown to prevent spam - async execute(interaction, client) { - const title = interaction.options.getString("title"); - const description = interaction.options.getString("description"); - const priority = interaction.options.getString("priority") || "medium"; - const attachment = interaction.options.getAttachment("attachment"); + data: new SlashCommandBuilder() + .setName("ticket") + .setDescription("🎫 Create a support ticket") + .addStringOption((option) => + option + .setName("title") + .setDescription("Brief title for your ticket") + .setRequired(true) + .setMaxLength(100), + ) + .addStringOption((option) => + option + .setName("description") + .setDescription("Detailed description of your issue") + .setRequired(true) + .setMaxLength(1000), + ) + .addStringOption((option) => + option + .setName("priority") + .setDescription("Priority level of your ticket") + .setRequired(false) + .addChoices( + { name: "🔴 High Priority", value: "high" }, + { name: "🟡 Medium Priority", value: "medium" }, + { name: "🟢 Low Priority", value: "low" }, + ), + ) + .addAttachmentOption((option) => + option + .setName("attachment") + .setDescription( + "Upload a file, screenshot, or proof related to your ticket", + ) + .setRequired(false), + ), + cooldown: 60, // 1 minute cooldown to prevent spam + async execute(interaction, client) { + const title = interaction.options.getString("title"); + const description = interaction.options.getString("description"); + const priority = interaction.options.getString("priority") || "medium"; + const attachment = interaction.options.getAttachment("attachment"); - await interaction.deferReply({ flags: 64 }); + await interaction.deferReply({ flags: 64 }); - const db = Database; // Use the exported instance -await db.ensureConnection(); // Ensure connection is established + const db = await Database.getInstance(); + await db.ensureConnection(); - try { - // 🏰 Get server configuration for ticket category - let config = await db.getServerConfig(interaction.guild.id); - let ticketCategory = null; + try { + // 🏰 Get server configuration for ticket category + let config = await db.getServerConfig(interaction.guild.id); + let ticketCategory = null; - // 📁 Find or create ticket category - if (config && config.ticket_category_id) { - ticketCategory = interaction.guild.channels.cache.get( - config.ticket_category_id - ); - } + // 📁 Find or create ticket category + if (config && config.ticket_category_id) { + ticketCategory = interaction.guild.channels.cache.get( + config.ticket_category_id, + ); + } - if (!ticketCategory) { - try { - ticketCategory = await interaction.guild.channels.create({ - name: "🎫 Support Tickets", - type: ChannelType.GuildCategory, - permissionOverwrites: [ - { - id: interaction.guild.roles.everyone.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: client.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.EmbedLinks, - ], - }, - ], - }); + if (!ticketCategory) { + try { + ticketCategory = await interaction.guild.channels.create({ + name: "🎫 Support Tickets", + type: ChannelType.GuildCategory, + permissionOverwrites: [ + { + id: interaction.guild.roles.everyone.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: client.user.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.EmbedLinks, + ], + }, + ], + }); - // Update config with new category - await db.updateServerConfig(interaction.guild.id, { - ticket_category_id: ticketCategory.id, - }); - } catch (error) { - console.error("❌ Error creating ticket category:", error); + // Update config with new category + await db.updateServerConfig(interaction.guild.id, { + ticket_category_id: ticketCategory.id, + }); + } catch (error) { + console.error("❌ Error creating ticket category:", error); - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Category Creation Failed") - .setDescription( - "Could not create ticket category. Please contact an administrator." - ) - .setFooter({ text: "Bot needs Manage Channels permission." }); + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Category Creation Failed") + .setDescription( + "Could not create ticket category. Please contact an administrator.", + ) + .setFooter({ text: "Bot needs Manage Channels permission." }); - return interaction.editReply({ embeds: [errorEmbed] }); - } - } + return interaction.editReply({ embeds: [errorEmbed] }); + } + } - // 🎫 Generate unique ticket ID and create channel - const ticketId = generateTicketId(); - const channelName = `ticket-${ticketId.toLowerCase()}`; + // 🎫 Generate unique ticket ID and create channel + const ticketId = generateTicketId(); + const channelName = `ticket-${ticketId.toLowerCase()}`; - const ticketChannel = await interaction.guild.channels.create({ - name: channelName, - type: ChannelType.GuildText, - parent: ticketCategory.id, - topic: `🎫 Support Ticket | ${title} | Created by ${interaction.user.tag}`, - permissionOverwrites: [ - { - id: interaction.guild.roles.everyone.id, - deny: [PermissionFlagsBits.ViewChannel], - }, - { - id: interaction.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.ReadMessageHistory, - PermissionFlagsBits.AttachFiles, - ], - }, - { - id: client.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.EmbedLinks, - PermissionFlagsBits.ManageChannels, - PermissionFlagsBits.ReadMessageHistory, - ], - }, - ], - }); + const ticketChannel = await interaction.guild.channels.create({ + name: channelName, + type: ChannelType.GuildText, + parent: ticketCategory.id, + topic: `🎫 Support Ticket | ${title} | Created by ${interaction.user.tag}`, + permissionOverwrites: [ + { + id: interaction.guild.roles.everyone.id, + deny: [PermissionFlagsBits.ViewChannel], + }, + { + id: interaction.user.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.ReadMessageHistory, + PermissionFlagsBits.AttachFiles, + ], + }, + { + id: client.user.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + PermissionFlagsBits.EmbedLinks, + PermissionFlagsBits.ManageChannels, + PermissionFlagsBits.ReadMessageHistory, + ], + }, + ], + }); - // 🗃️ Store ticket in database - const dbTicketId = await db.createTicket({ - guildId: interaction.guild.id, - channelId: ticketChannel.id, - userId: interaction.user.id, - title: title, - description: description, - priority: priority, - }); + // 🗃️ Store ticket in database + const dbTicketId = await db.createTicket({ + guildId: interaction.guild.id, + channelId: ticketChannel.id, + userId: interaction.user.id, + title: title, + description: description, + priority: priority, + }); - // 🎨 Create ticket embed - const ticketEmbed = new EmbedBuilder() - .setColor(getPriorityColor(priority, client.colors)) - .setTitle(`🎫 Support Ticket #${ticketId}`) - .setDescription(description) - .addFields( - { - name: "👤 Created by", - value: `${interaction.user}`, - inline: true, - }, - { - name: "📊 Priority", - value: `${priority.charAt(0).toUpperCase() + priority.slice(1)}`, - inline: true, - }, - { - name: "📅 Created", - value: ``, - inline: true, - }, - { - name: "📋 Title", - value: title, - inline: false, - } - ) - .setFooter({ - text: `Ticket ID: ${ticketId} | Use the buttons below to manage this ticket`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); + // 🎨 Create ticket embed + const ticketEmbed = new EmbedBuilder() + .setColor(getPriorityColor(priority, client.colors)) + .setTitle(`🎫 Support Ticket #${ticketId}`) + .setDescription(description) + .addFields( + { + name: "👤 Created by", + value: `${interaction.user}`, + inline: true, + }, + { + name: "📊 Priority", + value: `${priority.charAt(0).toUpperCase() + priority.slice(1)}`, + inline: true, + }, + { + name: "📅 Created", + value: ``, + inline: true, + }, + { + name: "📋 Title", + value: title, + inline: false, + }, + ) + .setFooter({ + text: `Ticket ID: ${ticketId} | Use the buttons below to manage this ticket`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); - // 📎 Add attachment info if provided - if (attachment) { - ticketEmbed.addFields({ - name: "📎 Attachment", - value: `[${attachment.name}](${attachment.url})`, - inline: false, - }); + // 📎 Add attachment info if provided + if (attachment) { + ticketEmbed.addFields({ + name: "📎 Attachment", + value: `[${attachment.name}](${attachment.url})`, + inline: false, + }); - // Store attachment in database - await db.addTicketMessage( - dbTicketId, - interaction.user.id, - "Attachment uploaded", - attachment.url - ); - } + // Store attachment in database + await db.addTicketMessage( + dbTicketId, + interaction.user.id, + "Attachment uploaded", + attachment.url, + ); + } - // 🎮 Create control buttons - const controlRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`ticket_claim_${dbTicketId}`) - .setLabel("🙋 Claim Ticket") - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId(`ticket_close_${dbTicketId}`) - .setLabel("🔒 Close Ticket") - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId(`ticket_priority_${dbTicketId}`) - .setLabel("📊 Change Priority") - .setStyle(ButtonStyle.Secondary) - ); + // 🎮 Create control buttons + const controlRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`ticket_claim_${dbTicketId}`) + .setLabel("🙋 Claim Ticket") + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId(`ticket_close_${dbTicketId}`) + .setLabel("🔒 Close Ticket") + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId(`ticket_priority_${dbTicketId}`) + .setLabel("📊 Change Priority") + .setStyle(ButtonStyle.Secondary), + ); - // 📤 Send ticket message - const ticketMessage = await ticketChannel.send({ - content: `${interaction.user} Welcome to your support ticket!`, - embeds: [ticketEmbed], - components: [controlRow], - }); + // 📤 Send ticket message + const ticketMessage = await ticketChannel.send({ + content: `${interaction.user} Welcome to your support ticket!`, + embeds: [ticketEmbed], + components: [controlRow], + }); - await ticketMessage.pin(); + await ticketMessage.pin(); - // 📬 Send confirmation to user - const confirmEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ Ticket Created Successfully") - .setDescription( - `Your support ticket has been created! Click the link below to access it.` - ) - .addFields( - { - name: "🎫 Ticket ID", - value: `#${ticketId}`, - inline: true, - }, - { - name: "📍 Channel", - value: `${ticketChannel}`, - inline: true, - }, - { - name: "📊 Priority", - value: priority.charAt(0).toUpperCase() + priority.slice(1), - inline: true, - } - ) - .setFooter({ text: "A moderator will assist you shortly!" }) - .setTimestamp(); + // 📬 Send confirmation to user + const confirmEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ Ticket Created Successfully") + .setDescription( + `Your support ticket has been created! Click the link below to access it.`, + ) + .addFields( + { + name: "🎫 Ticket ID", + value: `#${ticketId}`, + inline: true, + }, + { + name: "📍 Channel", + value: `${ticketChannel}`, + inline: true, + }, + { + name: "📊 Priority", + value: priority.charAt(0).toUpperCase() + priority.slice(1), + inline: true, + }, + ) + .setFooter({ text: "A moderator will assist you shortly!" }) + .setTimestamp(); - await interaction.editReply({ embeds: [confirmEmbed] }); + await interaction.editReply({ embeds: [confirmEmbed] }); - // 🔔 Notify moderators (if log channel is configured) - if (config && config.ticket_log_channel_id) { - const logChannel = interaction.guild.channels.cache.get( - config.ticket_log_channel_id - ); - if (logChannel) { - const notificationEmbed = new EmbedBuilder() - .setColor(getPriorityColor(priority, client.colors)) - .setTitle("🎫 New Support Ticket Created") - .setDescription( - `A new ${priority} priority ticket has been created.` - ) - .addFields( - { name: "👤 User", value: `${interaction.user}`, inline: true }, - { name: "📍 Channel", value: `${ticketChannel}`, inline: true }, - { name: "🎫 ID", value: `#${ticketId}`, inline: true }, - { name: "📋 Title", value: title, inline: false } - ) - .setTimestamp(); + // 🔔 Notify moderators (if log channel is configured) + if (config && config.ticket_log_channel_id) { + const logChannel = interaction.guild.channels.cache.get( + config.ticket_log_channel_id, + ); + if (logChannel) { + const notificationEmbed = new EmbedBuilder() + .setColor(getPriorityColor(priority, client.colors)) + .setTitle("🎫 New Support Ticket Created") + .setDescription( + `A new ${priority} priority ticket has been created.`, + ) + .addFields( + { name: "👤 User", value: `${interaction.user}`, inline: true }, + { name: "📍 Channel", value: `${ticketChannel}`, inline: true }, + { name: "🎫 ID", value: `#${ticketId}`, inline: true }, + { name: "📋 Title", value: title, inline: false }, + ) + .setTimestamp(); - await logChannel.send({ embeds: [notificationEmbed] }); - } - } + await logChannel.send({ embeds: [notificationEmbed] }); + } + } - console.log( - `🎫 Ticket #${ticketId} created by ${interaction.user.tag} in ${interaction.guild.name}` - ); - } catch (error) { - console.error("❌ Ticket creation error:", error); + console.log( + `🎫 Ticket #${ticketId} created by ${interaction.user.tag} in ${interaction.guild.name}`, + ); + } catch (error) { + console.error("❌ Ticket creation error:", error); - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Ticket Creation Failed") - .setDescription("An error occurred while creating your ticket.") - .addFields({ - name: "🔧 Possible Issues", - value: - "• Bot missing permissions\n• Server configuration error\n• Channel limit reached", - inline: false, - }) - .setFooter({ text: "Please contact a moderator for assistance." }); + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Ticket Creation Failed") + .setDescription("An error occurred while creating your ticket.") + .addFields({ + name: "🔧 Possible Issues", + value: + "• Bot missing permissions\n• Server configuration error\n• Channel limit reached", + inline: false, + }) + .setFooter({ text: "Please contact a moderator for assistance." }); - await interaction.editReply({ embeds: [errorEmbed] }); - } - }, + await interaction.editReply({ embeds: [errorEmbed] }); + } + }, }; diff --git a/commands/moderation/ticketdashboard.js b/commands/moderation/ticketdashboard.js index 8704f51..5a5ca72 100644 --- a/commands/moderation/ticketdashboard.js +++ b/commands/moderation/ticketdashboard.js @@ -1,351 +1,351 @@ const { - SlashCommandBuilder, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - StringSelectMenuBuilder, - PermissionFlagsBits, + SlashCommandBuilder, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + StringSelectMenuBuilder, + PermissionFlagsBits, } = require("discord.js"); const Database = require("../../utils/database"); const { - isModeratorOrOwner, - getPriorityColor, - formatTicketStatus, - timeAgo, -} = require("@adb/server/utils/moderation"); + isModeratorOrOwner, + getPriorityColor, + formatTicketStatus, + timeAgo, +} = require("../../utils/moderation"); module.exports = { - data: new SlashCommandBuilder() - .setName("ticketdashboard") - .setDescription("🎛️ Manage all server tickets (Moderators Only)") - .addSubcommand((subcommand) => - subcommand - .setName("list") - .setDescription("List all tickets") - .addStringOption((option) => - option - .setName("status") - .setDescription("Filter by ticket status") - .setRequired(false) - .addChoices( - { name: "🟢 Open", value: "open" }, - { name: "🟡 In Progress", value: "in_progress" }, - { name: "🟠 Waiting", value: "waiting" }, - { name: "🔴 Closed", value: "closed" }, - { name: "✅ Resolved", value: "resolved" } - ) - ) - ) - .addSubcommand((subcommand) => - subcommand.setName("stats").setDescription("View ticket statistics") - ) - .addSubcommand((subcommand) => - subcommand - .setName("manage") - .setDescription("Manage a specific ticket") - .addIntegerOption((option) => - option - .setName("ticket_id") - .setDescription("The ticket ID to manage") - .setRequired(true) - ) - ) - .addSubcommand((subcommand) => - subcommand - .setName("setup") - .setDescription("Configure ticket system settings") - ) - .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild), - cooldown: 5, - async execute(interaction, client) { - // 🛡️ Check if user is moderator - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Access Denied") - .setDescription( - "This command is only available to moderators and administrators." - ) - .setFooter({ - text: "Contact a server administrator if you need access.", - }); - - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } - - const subcommand = interaction.options.getSubcommand(); - const db = Database; // Use the exported instance -await db.ensureConnection(); // Ensure connection is established - - try { - switch (subcommand) { - case "list": - await handleList(interaction, client, db); - break; - case "stats": - await handleStats(interaction, client, db); - break; - case "manage": - await handleManage(interaction, client, db); - break; - case "setup": - await handleSetup(interaction, client, db); - break; - } - } catch (error) { - console.error("❌ Ticket dashboard error:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Dashboard Error") - .setDescription( - "An error occurred while accessing the ticket dashboard." - ) - .setFooter({ text: "Please try again or contact support." }); - - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); - } else { - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - } - }, + data: new SlashCommandBuilder() + .setName("ticketdashboard") + .setDescription("🎛️ Manage all server tickets (Moderators Only)") + .addSubcommand((subcommand) => + subcommand + .setName("list") + .setDescription("List all tickets") + .addStringOption((option) => + option + .setName("status") + .setDescription("Filter by ticket status") + .setRequired(false) + .addChoices( + { name: "🟢 Open", value: "open" }, + { name: "🟡 In Progress", value: "in_progress" }, + { name: "🟠 Waiting", value: "waiting" }, + { name: "🔴 Closed", value: "closed" }, + { name: "✅ Resolved", value: "resolved" }, + ), + ), + ) + .addSubcommand((subcommand) => + subcommand.setName("stats").setDescription("View ticket statistics"), + ) + .addSubcommand((subcommand) => + subcommand + .setName("manage") + .setDescription("Manage a specific ticket") + .addIntegerOption((option) => + option + .setName("ticket_id") + .setDescription("The ticket ID to manage") + .setRequired(true), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName("setup") + .setDescription("Configure ticket system settings"), + ) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild), + cooldown: 5, + async execute(interaction, client) { + // 🛡️ Check if user is moderator + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Access Denied") + .setDescription( + "This command is only available to moderators and administrators.", + ) + .setFooter({ + text: "Contact a server administrator if you need access.", + }); + + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } + + const subcommand = interaction.options.getSubcommand(); + const db = await Database.getInstance(); + await db.ensureConnection(); + + try { + switch (subcommand) { + case "list": + await handleList(interaction, client, db); + break; + case "stats": + await handleStats(interaction, client, db); + break; + case "manage": + await handleManage(interaction, client, db); + break; + case "setup": + await handleSetup(interaction, client, db); + break; + } + } catch (error) { + console.error("❌ Ticket dashboard error:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Dashboard Error") + .setDescription( + "An error occurred while accessing the ticket dashboard.", + ) + .setFooter({ text: "Please try again or contact support." }); + + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); + } else { + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); + } + } + }, }; // 📋 List tickets handler async function handleList(interaction, client, db) { - await interaction.deferReply({ ephemeral: true }); - - const status = interaction.options.getString("status"); - const tickets = await db.getTickets(interaction.guild.id, status); - - if (tickets.length === 0) { - const noTicketsEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("📋 No Tickets Found") - .setDescription( - status - ? `No tickets with status "${status}" found.` - : "No tickets found for this server." - ) - .setFooter({ text: "Users can create tickets with /ticket command." }) - .setTimestamp(); - - return interaction.editReply({ embeds: [noTicketsEmbed] }); - } - - // 📊 Paginate tickets (show 10 per page) - const ticketsPerPage = 10; - const totalPages = Math.ceil(tickets.length / ticketsPerPage); - let currentPage = 0; - - const generateTicketList = (page) => { - const start = page * ticketsPerPage; - const end = start + ticketsPerPage; - const pageTickets = tickets.slice(start, end); - - const listEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle(`🎫 Ticket Dashboard ${status ? `(${status})` : ""}`) - .setDescription( - `Showing ${pageTickets.length} of ${tickets.length} tickets` - ) - .setFooter({ - text: `Page ${ - page + 1 - } of ${totalPages} • Use /ticketdashboard manage to manage specific tickets`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - pageTickets.forEach((ticket) => { - const user = client.users.cache.get(ticket.user_id); - const channel = interaction.guild.channels.cache.get(ticket.channel_id); - - listEmbed.addFields({ - name: `🎫 Ticket #${ticket.id} - ${ticket.title}`, - value: ` + await interaction.deferReply({ ephemeral: true }); + + const status = interaction.options.getString("status"); + const tickets = await db.getTickets(interaction.guild.id, status); + + if (tickets.length === 0) { + const noTicketsEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("📋 No Tickets Found") + .setDescription( + status + ? `No tickets with status "${status}" found.` + : "No tickets found for this server.", + ) + .setFooter({ text: "Users can create tickets with /ticket command." }) + .setTimestamp(); + + return interaction.editReply({ embeds: [noTicketsEmbed] }); + } + + // 📊 Paginate tickets (show 10 per page) + const ticketsPerPage = 10; + const totalPages = Math.ceil(tickets.length / ticketsPerPage); + let currentPage = 0; + + const generateTicketList = (page) => { + const start = page * ticketsPerPage; + const end = start + ticketsPerPage; + const pageTickets = tickets.slice(start, end); + + const listEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle(`🎫 Ticket Dashboard ${status ? `(${status})` : ""}`) + .setDescription( + `Showing ${pageTickets.length} of ${tickets.length} tickets`, + ) + .setFooter({ + text: `Page ${ + page + 1 + } of ${totalPages} • Use /ticketdashboard manage to manage specific tickets`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + pageTickets.forEach((ticket) => { + const user = client.users.cache.get(ticket.user_id); + const channel = interaction.guild.channels.cache.get(ticket.channel_id); + + listEmbed.addFields({ + name: `🎫 Ticket #${ticket.id} - ${ticket.title}`, + value: ` **Status:** ${formatTicketStatus(ticket.status)} **Priority:** ${ - ticket.priority.charAt(0).toUpperCase() + - ticket.priority.slice(1) - } + ticket.priority.charAt(0).toUpperCase() + + ticket.priority.slice(1) + } **User:** ${user ? user.tag : "Unknown User"} **Channel:** ${channel ? channel : "Deleted Channel"} **Created:** ${timeAgo(ticket.created_at)} ${ - ticket.moderator_id - ? `**Assigned:** <@${ticket.moderator_id}>` - : "**Assigned:** Unassigned" - } + ticket.moderator_id + ? `**Assigned:** <@${ticket.moderator_id}>` + : "**Assigned:** Unassigned" + } `, - inline: false, - }); - }); - - return listEmbed; - }; - - const listEmbed = generateTicketList(currentPage); - - // 🎮 Navigation buttons for pagination - const navigationRow = new ActionRowBuilder(); - - if (totalPages > 1) { - navigationRow.addComponents( - new ButtonBuilder() - .setCustomId("ticket_list_prev") - .setLabel("◀️ Previous") - .setStyle(ButtonStyle.Secondary) - .setDisabled(currentPage === 0), - new ButtonBuilder() - .setCustomId("ticket_list_next") - .setLabel("Next ▶️") - .setStyle(ButtonStyle.Secondary) - .setDisabled(currentPage === totalPages - 1) - ); - } - - const components = totalPages > 1 ? [navigationRow] : []; - await interaction.editReply({ embeds: [listEmbed], components }); + inline: false, + }); + }); + + return listEmbed; + }; + + const listEmbed = generateTicketList(currentPage); + + // 🎮 Navigation buttons for pagination + const navigationRow = new ActionRowBuilder(); + + if (totalPages > 1) { + navigationRow.addComponents( + new ButtonBuilder() + .setCustomId("ticket_list_prev") + .setLabel("◀️ Previous") + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === 0), + new ButtonBuilder() + .setCustomId("ticket_list_next") + .setLabel("Next ▶️") + .setStyle(ButtonStyle.Secondary) + .setDisabled(currentPage === totalPages - 1), + ); + } + + const components = totalPages > 1 ? [navigationRow] : []; + await interaction.editReply({ embeds: [listEmbed], components }); } // 📊 Stats handler async function handleStats(interaction, client, db) { - await interaction.deferReply({ ephemeral: true }); - - const allTickets = await db.getTickets(interaction.guild.id); - - // 📈 Calculate statistics - const stats = { - total: allTickets.length, - open: allTickets.filter((t) => t.status === "open").length, - inProgress: allTickets.filter((t) => t.status === "in_progress").length, - waiting: allTickets.filter((t) => t.status === "waiting").length, - closed: allTickets.filter((t) => t.status === "closed").length, - resolved: allTickets.filter((t) => t.status === "resolved").length, - high: allTickets.filter((t) => t.priority === "high").length, - medium: allTickets.filter((t) => t.priority === "medium").length, - low: allTickets.filter((t) => t.priority === "low").length, - }; - - // 📅 Recent activity - const last24h = allTickets.filter( - (t) => new Date(t.created_at) > new Date(Date.now() - 24 * 60 * 60 * 1000) - ).length; - - const last7d = allTickets.filter( - (t) => - new Date(t.created_at) > new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) - ).length; - - const statsEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle("📊 Ticket Statistics") - .setDescription( - `Comprehensive ticket analytics for ${interaction.guild.name}` - ) - .addFields( - { - name: "📋 Status Overview", - value: ` + await interaction.deferReply({ ephemeral: true }); + + const allTickets = await db.getTickets(interaction.guild.id); + + // 📈 Calculate statistics + const stats = { + total: allTickets.length, + open: allTickets.filter((t) => t.status === "open").length, + inProgress: allTickets.filter((t) => t.status === "in_progress").length, + waiting: allTickets.filter((t) => t.status === "waiting").length, + closed: allTickets.filter((t) => t.status === "closed").length, + resolved: allTickets.filter((t) => t.status === "resolved").length, + high: allTickets.filter((t) => t.priority === "high").length, + medium: allTickets.filter((t) => t.priority === "medium").length, + low: allTickets.filter((t) => t.priority === "low").length, + }; + + // 📅 Recent activity + const last24h = allTickets.filter( + (t) => new Date(t.created_at) > new Date(Date.now() - 24 * 60 * 60 * 1000), + ).length; + + const last7d = allTickets.filter( + (t) => + new Date(t.created_at) > new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), + ).length; + + const statsEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle("📊 Ticket Statistics") + .setDescription( + `Comprehensive ticket analytics for ${interaction.guild.name}`, + ) + .addFields( + { + name: "📋 Status Overview", + value: ` 🟢 Open: **${stats.open}** 🟡 In Progress: **${stats.inProgress}** 🟠 Waiting: **${stats.waiting}** 🔴 Closed: **${stats.closed}** ✅ Resolved: **${stats.resolved}** `, - inline: true, - }, - { - name: "📊 Priority Breakdown", - value: ` + inline: true, + }, + { + name: "📊 Priority Breakdown", + value: ` 🔴 High: **${stats.high}** 🟡 Medium: **${stats.medium}** 🟢 Low: **${stats.low}** `, - inline: true, - }, - { - name: "📈 Activity", - value: ` + inline: true, + }, + { + name: "📈 Activity", + value: ` **Total Tickets:** ${stats.total} **Last 24h:** ${last24h} **Last 7 days:** ${last7d} `, - inline: true, - } - ) - .setFooter({ - text: `Generated by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - // 📊 Add progress bars for visual representation - if (stats.total > 0) { - const activeTickets = stats.open + stats.inProgress + stats.waiting; - const completedTickets = stats.closed + stats.resolved; - - statsEmbed.addFields({ - name: "📈 Progress Overview", - value: ` + inline: true, + }, + ) + .setFooter({ + text: `Generated by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + // 📊 Add progress bars for visual representation + if (stats.total > 0) { + const activeTickets = stats.open + stats.inProgress + stats.waiting; + const completedTickets = stats.closed + stats.resolved; + + statsEmbed.addFields({ + name: "📈 Progress Overview", + value: ` **Active Tickets:** ${activeTickets}/${stats.total} (${Math.round( - (activeTickets / stats.total) * 100 - )}%) + (activeTickets / stats.total) * 100, + )}%) **Completed:** ${completedTickets}/${stats.total} (${Math.round( - (completedTickets / stats.total) * 100 - )}%) + (completedTickets / stats.total) * 100, + )}%) `, - inline: false, - }); - } + inline: false, + }); + } - await interaction.editReply({ embeds: [statsEmbed] }); + await interaction.editReply({ embeds: [statsEmbed] }); } // 🛠️ Manage specific ticket handler async function handleManage(interaction, client, db) { - const ticketId = interaction.options.getInteger("ticket_id"); + const ticketId = interaction.options.getInteger("ticket_id"); - // Implementation for managing specific tickets would go here - // This would include status changes, assignment, etc. + // Implementation for managing specific tickets would go here + // This would include status changes, assignment, etc. - const manageEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle(`🛠️ Managing Ticket #${ticketId}`) - .setDescription("Ticket management interface") - .setFooter({ text: "Advanced ticket management features coming soon!" }); + const manageEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle(`🛠️ Managing Ticket #${ticketId}`) + .setDescription("Ticket management interface") + .setFooter({ text: "Advanced ticket management features coming soon!" }); - await interaction.reply({ embeds: [manageEmbed], ephemeral: true }); + await interaction.reply({ embeds: [manageEmbed], ephemeral: true }); } // ⚙️ Setup handler async function handleSetup(interaction, client, db) { - const setupEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle("⚙️ Ticket System Setup") - .setDescription("Configure your server's ticket system") - .addFields( - { - name: "📁 Category Channel", - value: "Tickets will be created in a dedicated category", - inline: false, - }, - { - name: "📢 Log Channel", - value: "Set a channel for ticket notifications", - inline: false, - }, - { - name: "🎯 Current Status", - value: "Ticket system is active and ready to use!", - inline: false, - } - ) - .setFooter({ text: "Advanced setup options coming soon!" }) - .setTimestamp(); - - await interaction.reply({ embeds: [setupEmbed], ephemeral: true }); + const setupEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle("⚙️ Ticket System Setup") + .setDescription("Configure your server's ticket system") + .addFields( + { + name: "📁 Category Channel", + value: "Tickets will be created in a dedicated category", + inline: false, + }, + { + name: "📢 Log Channel", + value: "Set a channel for ticket notifications", + inline: false, + }, + { + name: "🎯 Current Status", + value: "Ticket system is active and ready to use!", + inline: false, + }, + ) + .setFooter({ text: "Advanced setup options coming soon!" }) + .setTimestamp(); + + await interaction.reply({ embeds: [setupEmbed], ephemeral: true }); } diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 674d71c..5d8b2ad 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -1,1287 +1,1312 @@ const { - Events, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - StringSelectMenuBuilder, + Events, + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + StringSelectMenuBuilder, } = require("discord.js"); const Database = require("../utils/database"); module.exports = { - name: Events.InteractionCreate, - async execute(interaction, client) { - // 🎯 Handle slash commands - if (interaction.isChatInputCommand()) { - const command = interaction.client.commands.get(interaction.commandName); - - if (!command) { - console.error(`❌ No command matching ${interaction.commandName} was found.`); - return; - } - - // 🔄 Cooldown system - const { cooldowns } = interaction.client; - - if (!cooldowns.has(command.data.name)) { - cooldowns.set(command.data.name, new Map()); - } - - const now = Date.now(); - const timestamps = cooldowns.get(command.data.name); - const defaultCooldownDuration = 3; - const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1000; - - if (timestamps.has(interaction.user.id)) { - const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; - - if (now < expirationTime) { - const expiredTimestamp = Math.round(expirationTime / 1000); - - const cooldownEmbed = new EmbedBuilder() - .setColor("#FFA500") - .setTitle("⏱️ Slow down there!") - .setDescription( - `Please wait before using \`/${command.data.name}\` again.` - ) - .setTimestamp(); - - return interaction.reply({ - embeds: [cooldownEmbed], - flags: 64, // MessageFlags.Ephemeral - }); - } - } - - timestamps.set(interaction.user.id, now); - setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); - - // 🛡️ Execute command with error handling - try { - await command.execute(interaction, client); - } catch (error) { - console.error(`❌ Error executing ${interaction.commandName}:`, error); - - const errorEmbed = new EmbedBuilder() - .setColor("#FF0000") - .setTitle("⚠️ Something went wrong!") - .setDescription( - "There was an error while executing this command. Please try again later." - ) - .setTimestamp(); - - try { - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ - embeds: [errorEmbed], - flags: 64, // MessageFlags.Ephemeral - }); - } else { - await interaction.reply({ - embeds: [errorEmbed], - flags: 64, // MessageFlags.Ephemeral - }); - } - } catch (replyError) { - console.error("Failed to send error message:", replyError); - } - } - } - - // 🎮 Handle button interactions - if (interaction.isButton()) { - // Handle help menu navigation - if (interaction.customId.startsWith("help_")) { - await handleHelpNavigation(interaction, client); - } - - // Handle feedback interactions - if (interaction.customId.startsWith("feedback_")) { - await handleFeedbackInteraction(interaction, client); - } - - // Handle AI context modal - if (interaction.customId === "show_context_modal") { - await showContextModal(interaction, client); - } - - // Handle Truth or Dare buttons - if (interaction.customId.startsWith("tod_")) { - await handleTruthOrDareButton(interaction, client); - } - - // Handle AI assistant buttons - if (interaction.customId.startsWith("ai_ask_again_")) { - await handleAIAskAgain(interaction, client); - } - - if (interaction.customId.startsWith("ai_feedback_")) { - await handleAIFeedback(interaction, client); - } - - // Handle AI rating buttons - if (interaction.customId.startsWith("ai_rate_")) { - await handleAIRating(interaction, client); - } - - // Handle ticket system buttons - if (interaction.customId.startsWith("ticket_")) { - await handleTicketButtons(interaction, client); - } - - // Handle reminder buttons - if (interaction.customId.startsWith("reminder_")) { - await handleReminderButtons(interaction, client); - } - } - - // 📋 Handle select menu interactions - if (interaction.isStringSelectMenu()) { - if (interaction.customId === "feedback_select") { - await handleFeedbackSelection(interaction, client); - } - } - - // 📝 Handle modal submissions - if (interaction.isModalSubmit()) { - if (interaction.customId === "feedback_submit") { - await handleFeedbackSubmission(interaction, client); - } - - // Handle AI context modal (if it exists) - if (interaction.customId === "ai_context_modal") { - await handleAIContextModal(interaction, client); - } - - // Handle AI ask modal submission - if (interaction.customId === "ai_ask_modal") { - await handleAIAskModal(interaction, client); - } - - // Handle ticket closing modal - if (interaction.customId.startsWith("close_ticket_modal_")) { - await handleCloseTicketModal(interaction, client); - } - } - }, + name: Events.InteractionCreate, + async execute(interaction, client) { + // 🎯 Handle slash commands + if (interaction.isChatInputCommand()) { + const command = interaction.client.commands.get(interaction.commandName); + + if (!command) { + console.error( + `❌ No command matching ${interaction.commandName} was found.`, + ); + return; + } + + // 🔄 Cooldown system + const { cooldowns } = interaction.client; + + if (!cooldowns.has(command.data.name)) { + cooldowns.set(command.data.name, new Map()); + } + + const now = Date.now(); + const timestamps = cooldowns.get(command.data.name); + const defaultCooldownDuration = 3; + const cooldownAmount = + (command.cooldown ?? defaultCooldownDuration) * 1000; + + if (timestamps.has(interaction.user.id)) { + const expirationTime = + timestamps.get(interaction.user.id) + cooldownAmount; + + if (now < expirationTime) { + const expiredTimestamp = Math.round(expirationTime / 1000); + + const cooldownEmbed = new EmbedBuilder() + .setColor("#FFA500") + .setTitle("⏱️ Slow down there!") + .setDescription( + `Please wait before using \`/${command.data.name}\` again.`, + ) + .setTimestamp(); + + return interaction.reply({ + embeds: [cooldownEmbed], + flags: 64, // MessageFlags.Ephemeral + }); + } + } + + timestamps.set(interaction.user.id, now); + setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); + + // 🛡️ Execute command with error handling + try { + await command.execute(interaction, client); + } catch (error) { + console.error(`❌ Error executing ${interaction.commandName}:`, error); + + const errorEmbed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("⚠️ Something went wrong!") + .setDescription( + "There was an error while executing this command. Please try again later.", + ) + .setTimestamp(); + + try { + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + embeds: [errorEmbed], + flags: 64, // MessageFlags.Ephemeral + }); + } else { + await interaction.reply({ + embeds: [errorEmbed], + flags: 64, // MessageFlags.Ephemeral + }); + } + } catch (replyError) { + console.error("Failed to send error message:", replyError); + } + } + } + + // 🎮 Handle button interactions + if (interaction.isButton()) { + // Handle help menu navigation + if (interaction.customId.startsWith("help_")) { + await handleHelpNavigation(interaction, client); + } + + // Handle feedback interactions + if (interaction.customId.startsWith("feedback_")) { + await handleFeedbackInteraction(interaction, client); + } + + // Handle AI context modal + if (interaction.customId === "show_context_modal") { + await showContextModal(interaction, client); + } + + // Handle Truth or Dare buttons + if (interaction.customId.startsWith("tod_")) { + await handleTruthOrDareButton(interaction, client); + } + + // Handle AI assistant buttons + if (interaction.customId.startsWith("ai_ask_again_")) { + await handleAIAskAgain(interaction, client); + } + + if (interaction.customId.startsWith("ai_feedback_")) { + await handleAIFeedback(interaction, client); + } + + // Handle AI rating buttons + if (interaction.customId.startsWith("ai_rate_")) { + await handleAIRating(interaction, client); + } + + // Handle ticket system buttons + if (interaction.customId.startsWith("ticket_")) { + await handleTicketButtons(interaction, client); + } + + // Handle reminder buttons + if (interaction.customId.startsWith("reminder_")) { + await handleReminderButtons(interaction, client); + } + } + + // 📋 Handle select menu interactions + if (interaction.isStringSelectMenu()) { + if (interaction.customId === "feedback_select") { + await handleFeedbackSelection(interaction, client); + } + } + + // 📝 Handle modal submissions + if (interaction.isModalSubmit()) { + if (interaction.customId === "feedback_submit") { + await handleFeedbackSubmission(interaction, client); + } + + // Handle AI context modal (if it exists) + if (interaction.customId === "ai_context_modal") { + await handleAIContextModal(interaction, client); + } + + // Handle AI ask modal submission + if (interaction.customId === "ai_ask_modal") { + await handleAIAskModal(interaction, client); + } + + // Handle ticket closing modal + if (interaction.customId.startsWith("close_ticket_modal_")) { + await handleCloseTicketModal(interaction, client); + } + } + }, }; // 📚 Help navigation handler async function handleHelpNavigation(interaction, client) { - const { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - } = require("discord.js"); - const helpCommand = require("../commands/general/help"); - - const category = interaction.customId.split("_")[1]; - - if (category === "refresh") { - // Return to main help menu - return await helpCommand.execute(interaction, client); - } - - const categoryData = helpCommand.getCommands[category]; - if (!categoryData) { - return await interaction.reply({ - content: "❌ Category not found!", - flags: 64, - }); - } - - const categoryEmbed = new EmbedBuilder() - .setColor(categoryData.color) - .setTitle(categoryData.title) - .setDescription(categoryData.description) - .addFields({ - name: "📋 Available Commands", - value: categoryData.commands.join("\n"), - inline: false, - }) - .setFooter({ - text: `Requested by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - const backRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("help_refresh") - .setLabel("◀️ Back to Menu") - .setStyle(ButtonStyle.Secondary) - ); - - await interaction.update({ - embeds: [categoryEmbed], - components: [backRow], - }); + const { + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + } = require("discord.js"); + const helpCommand = require("../commands/general/help"); + + const category = interaction.customId.split("_")[1]; + + if (category === "refresh") { + // Return to main help menu + return await helpCommand.execute(interaction, client); + } + + const categoryData = helpCommand.getCommands[category]; + if (!categoryData) { + return await interaction.reply({ + content: "❌ Category not found!", + flags: 64, + }); + } + + const categoryEmbed = new EmbedBuilder() + .setColor(categoryData.color) + .setTitle(categoryData.title) + .setDescription(categoryData.description) + .addFields({ + name: "📋 Available Commands", + value: categoryData.commands.join("\n"), + inline: false, + }) + .setFooter({ + text: `Requested by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + const backRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("help_refresh") + .setLabel("◀️ Back to Menu") + .setStyle(ButtonStyle.Secondary), + ); + + await interaction.update({ + embeds: [categoryEmbed], + components: [backRow], + }); } // 📝 Feedback interaction handler async function handleFeedbackInteraction(interaction, client) { - const { - EmbedBuilder, - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, - } = require("discord.js"); - - if (interaction.customId === "feedback_modal") { - const modal = new ModalBuilder() - .setCustomId("feedback_submit") - .setTitle("📝 Send Feedback"); - - const typeInput = new TextInputBuilder() - .setCustomId("feedback_type") - .setLabel("Feedback Type") - .setStyle(TextInputStyle.Short) - .setPlaceholder("Bug Report, Feature Request, General Feedback, etc.") - .setRequired(true) - .setMaxLength(50); - - const titleInput = new TextInputBuilder() - .setCustomId("feedback_title") - .setLabel("Title") - .setStyle(TextInputStyle.Short) - .setPlaceholder("Brief title for your feedback") - .setRequired(true) - .setMaxLength(100); - - const descriptionInput = new TextInputBuilder() - .setCustomId("feedback_description") - .setLabel("Description") - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder("Detailed description of your feedback...") - .setRequired(true) - .setMaxLength(1000); - - const contactInput = new TextInputBuilder() - .setCustomId("feedback_contact") - .setLabel("Contact Info (Optional)") - .setStyle(TextInputStyle.Short) - .setPlaceholder("Discord tag, email, etc. (optional)") - .setRequired(false) - .setMaxLength(100); - - const row1 = new ActionRowBuilder().addComponents(typeInput); - const row2 = new ActionRowBuilder().addComponents(titleInput); - const row3 = new ActionRowBuilder().addComponents(descriptionInput); - const row4 = new ActionRowBuilder().addComponents(contactInput); - - modal.addComponents(row1, row2, row3, row4); - await interaction.showModal(modal); - } + const { + EmbedBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, + } = require("discord.js"); + + if (interaction.customId === "feedback_modal") { + const modal = new ModalBuilder() + .setCustomId("feedback_submit") + .setTitle("📝 Send Feedback"); + + const typeInput = new TextInputBuilder() + .setCustomId("feedback_type") + .setLabel("Feedback Type") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Bug Report, Feature Request, General Feedback, etc.") + .setRequired(true) + .setMaxLength(50); + + const titleInput = new TextInputBuilder() + .setCustomId("feedback_title") + .setLabel("Title") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Brief title for your feedback") + .setRequired(true) + .setMaxLength(100); + + const descriptionInput = new TextInputBuilder() + .setCustomId("feedback_description") + .setLabel("Description") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Detailed description of your feedback...") + .setRequired(true) + .setMaxLength(1000); + + const contactInput = new TextInputBuilder() + .setCustomId("feedback_contact") + .setLabel("Contact Info (Optional)") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Discord tag, email, etc. (optional)") + .setRequired(false) + .setMaxLength(100); + + const row1 = new ActionRowBuilder().addComponents(typeInput); + const row2 = new ActionRowBuilder().addComponents(titleInput); + const row3 = new ActionRowBuilder().addComponents(descriptionInput); + const row4 = new ActionRowBuilder().addComponents(contactInput); + + modal.addComponents(row1, row2, row3, row4); + await interaction.showModal(modal); + } } // 📋 Feedback selection handler async function handleFeedbackSelection(interaction, client) { - const { EmbedBuilder } = require("discord.js"); - - const feedbackType = interaction.values[0]; - - const embed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("📝 Feedback Form") - .setDescription( - `You selected: **${feedbackType}**\n\nPlease fill out the form that will appear.` - ) - .setFooter({ text: "Thank you for helping us improve!" }); - - await interaction.reply({ - embeds: [embed], - flags: 64, - }); + const { EmbedBuilder } = require("discord.js"); + + const feedbackType = interaction.values[0]; + + const embed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("📝 Feedback Form") + .setDescription( + `You selected: **${feedbackType}**\n\nPlease fill out the form that will appear.`, + ) + .setFooter({ text: "Thank you for helping us improve!" }); + + await interaction.reply({ + embeds: [embed], + flags: 64, + }); } // 📝 Feedback submission handler async function handleFeedbackSubmission(interaction, client) { - const { EmbedBuilder } = require("discord.js"); - - const feedbackType = interaction.fields.getTextInputValue("feedback_type"); - const title = interaction.fields.getTextInputValue("feedback_title"); - const description = interaction.fields.getTextInputValue("feedback_description"); - const contact = - interaction.fields.getTextInputValue("feedback_contact") || "Not provided"; - - // Create feedback embed for developers - const feedbackEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle(`📝 New Feedback: ${feedbackType}`) - .setDescription(title) - .addFields( - { - name: "📋 Description", - value: description, - inline: false, - }, - { - name: "👤 User", - value: `${interaction.user.tag} (${interaction.user.id})`, - inline: true, - }, - { - name: "🏠 Server", - value: `${interaction.guild.name} (${interaction.guild.id})`, - inline: true, - }, - { - name: "📞 Contact", - value: contact, - inline: true, - } - ) - .setThumbnail(interaction.user.displayAvatarURL()) - .setTimestamp(); - - // Send to feedback channel (you can configure this) - // const feedbackChannel = client.channels.cache.get("YOUR_FEEDBACK_CHANNEL_ID"); - // if (feedbackChannel) { - // await feedbackChannel.send({ embeds: [feedbackEmbed] }); - // } - - // Log to console for now - console.log("📝 New Feedback Received:", { - type: feedbackType, - title, - user: interaction.user.tag, - server: interaction.guild.name, - }); - - // Confirm to user - const confirmEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ Feedback Submitted!") - .setDescription("Thank you for your feedback! Our team will review it soon.") - .addFields({ - name: "📋 Your Submission", - value: `**Type:** ${feedbackType}\n**Title:** ${title}`, - inline: false, - }) - .setFooter({ text: "We appreciate your input!" }); - - await interaction.reply({ - embeds: [confirmEmbed], - flags: 64, - }); + const { EmbedBuilder } = require("discord.js"); + + const feedbackType = interaction.fields.getTextInputValue("feedback_type"); + const title = interaction.fields.getTextInputValue("feedback_title"); + const description = interaction.fields.getTextInputValue( + "feedback_description", + ); + const contact = + interaction.fields.getTextInputValue("feedback_contact") || "Not provided"; + + // Create feedback embed for developers + const feedbackEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle(`📝 New Feedback: ${feedbackType}`) + .setDescription(title) + .addFields( + { + name: "📋 Description", + value: description, + inline: false, + }, + { + name: "👤 User", + value: `${interaction.user.tag} (${interaction.user.id})`, + inline: true, + }, + { + name: "🏠 Server", + value: `${interaction.guild.name} (${interaction.guild.id})`, + inline: true, + }, + { + name: "📞 Contact", + value: contact, + inline: true, + }, + ) + .setThumbnail(interaction.user.displayAvatarURL()) + .setTimestamp(); + + // Send to feedback channel (you can configure this) + // const feedbackChannel = client.channels.cache.get("YOUR_FEEDBACK_CHANNEL_ID"); + // if (feedbackChannel) { + // await feedbackChannel.send({ embeds: [feedbackEmbed] }); + // } + + // Log to console for now + console.log("📝 New Feedback Received:", { + type: feedbackType, + title, + user: interaction.user.tag, + server: interaction.guild.name, + }); + + // Confirm to user + const confirmEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ Feedback Submitted!") + .setDescription( + "Thank you for your feedback! Our team will review it soon.", + ) + .addFields({ + name: "📋 Your Submission", + value: `**Type:** ${feedbackType}\n**Title:** ${title}`, + inline: false, + }) + .setFooter({ text: "We appreciate your input!" }); + + await interaction.reply({ + embeds: [confirmEmbed], + flags: 64, + }); } // 🤖 AI Context modal handler async function handleAIContextModal(interaction, client) { - const { EmbedBuilder } = require("discord.js"); - const Database = require("@adb/server/utils/database"); - - const context = interaction.fields.getTextInputValue("ai_context_input"); - - try { - const db = await Database.getInstance(); - await db.updateServerConfig(interaction.guild.id, { - ai_context: context, - }); - - const successEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ AI Context Updated") - .setDescription("Successfully updated the AI assistant context for your server.") - .addFields({ - name: "📝 Context Preview", - value: context.substring(0, 500) + (context.length > 500 ? "..." : ""), - inline: false, - }) - .setFooter({ - text: `Updated by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - await interaction.reply({ - embeds: [successEmbed], - flags: 64, - }); - } catch (error) { - console.error("❌ Error updating AI context:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Error") - .setDescription("Failed to update AI context. Please try again later."); - - await interaction.reply({ - embeds: [errorEmbed], - flags: 64, - }); - } + const { EmbedBuilder } = require("discord.js"); + const Database = require("../utils/database"); + + const context = interaction.fields.getTextInputValue("ai_context_input"); + + try { + const db = await Database.getInstance(); + await db.updateServerConfig(interaction.guild.id, { + ai_context: context, + }); + + const successEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ AI Context Updated") + .setDescription( + "Successfully updated the AI assistant context for your server.", + ) + .addFields({ + name: "📝 Context Preview", + value: context.substring(0, 500) + (context.length > 500 ? "..." : ""), + inline: false, + }) + .setFooter({ + text: `Updated by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await interaction.reply({ + embeds: [successEmbed], + flags: 64, + }); + } catch (error) { + console.error("❌ Error updating AI context:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Error") + .setDescription("Failed to update AI context. Please try again later."); + + await interaction.reply({ + embeds: [errorEmbed], + flags: 64, + }); + } } // 🤖 Show context modal async function showContextModal(interaction, client) { - const { - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, - } = require("discord.js"); - - const modal = new ModalBuilder() - .setCustomId("ai_context_modal") - .setTitle("🤖 Set AI Assistant Context"); - - const contextInput = new TextInputBuilder() - .setCustomId("ai_context_input") - .setLabel("Server Information & FAQs") - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder( - "Enter server rules, FAQs, information that the AI should know about your server...\n\n" + - "Example:\n" + - "Server Rules:\n1. Be respectful\n2. No spam\n\n" + - "FAQ:\nQ: How to get verified?\nA: Use /verify command" - ) - .setRequired(true) - .setMaxLength(3000); - - const row = new ActionRowBuilder().addComponents(contextInput); - modal.addComponents(row); - - await interaction.showModal(modal); + const { + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, + } = require("discord.js"); + + const modal = new ModalBuilder() + .setCustomId("ai_context_modal") + .setTitle("🤖 Set AI Assistant Context"); + + const contextInput = new TextInputBuilder() + .setCustomId("ai_context_input") + .setLabel("Server Information & FAQs") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder( + "Enter server rules, FAQs, information that the AI should know about your server...\n\n" + + "Example:\n" + + "Server Rules:\n1. Be respectful\n2. No spam\n\n" + + "FAQ:\nQ: How to get verified?\nA: Use /verify command", + ) + .setRequired(true) + .setMaxLength(3000); + + const row = new ActionRowBuilder().addComponents(contextInput); + modal.addComponents(row); + + await interaction.showModal(modal); } // 🎮 Handle Truth or Dare button interactions async function handleTruthOrDareButton(interaction, client) { - const { EmbedBuilder, MessageFlags } = require("discord.js"); - const Database = require("@adb/server/utils/database"); - const { - getRandomTruthOrDare, - } = require("../commands/truth-or-dare/truthordare"); - - const customIdParts = interaction.customId.split("_"); - const action = customIdParts[1]; // "truth", "dare", "random", "rules", "stats" - const targetUserId = customIdParts[2]; - - // Handle non-game actions first - if (action === "rules") { - const rulesEmbed = new EmbedBuilder() - .setColor("#4287f5") - .setTitle("📋 Truth or Dare Rules") - .setDescription("Keep it fun and respectful for everyone!") - .addFields( - { - name: "✅ Do's", - value: - "• Be honest with truths\n• Complete dares safely\n• Respect others' boundaries\n• Keep it appropriate for the server", - inline: false, - }, - { - name: "❌ Don'ts", - value: - "• Share inappropriate content\n• Do anything harmful or illegal\n• Force participation\n• Break server rules", - inline: false, - }, - { - name: "🛡️ Safety First", - value: - "If you're uncomfortable with a truth or dare, you can always skip it. Your safety and comfort matter most!", - inline: false, - } - ) - .setFooter({ text: "Have fun and play responsibly!" }); - - return interaction.reply({ - embeds: [rulesEmbed], - flags: MessageFlags.Ephemeral, - }); - } - - if (action === "stats") { - try { - const db = await Database.getInstance(); - const config = await db.TruthOrDareConfig.findOne({ - guildId: interaction.guild.id, - }); - - const customTruths = config?.customTruths?.length || 0; - const customDares = config?.customDares?.length || 0; - const totalCustom = customTruths + customDares; - - const statsEmbed = new EmbedBuilder() - .setColor("#00ff88") - .setTitle("📊 Truth or Dare Server Stats") - .setDescription(`Statistics for **${interaction.guild.name}**`) - .addFields( - { - name: "💭 Custom Truths", - value: `${customTruths}`, - inline: true, - }, - { - name: "🎯 Custom Dares", - value: `${customDares}`, - inline: true, - }, - { - name: "🎮 Total Custom Content", - value: `${totalCustom}`, - inline: true, - }, - { - name: "📚 Default Content", - value: "15 truths, 15 dares", - inline: true, - }, - { - name: "🎲 Total Available", - value: `${30 + totalCustom} questions/dares`, - inline: true, - }, - { - name: "⚙️ Status", - value: config?.enabled !== false ? "✅ Enabled" : "❌ Disabled", - inline: true, - } - ) - .setFooter({ text: "Use /truthordare add to contribute content!" }); - - return interaction.reply({ - embeds: [statsEmbed], - flags: MessageFlags.Ephemeral, - }); - } catch (error) { - console.error("Error fetching ToD stats:", error); - return interaction.reply({ - content: "❌ Failed to fetch server stats.", - flags: MessageFlags.Ephemeral, - }); - } - } - - // Check if the user clicking is the target user or the original user - if ( - interaction.user.id !== targetUserId && - interaction.user.id !== interaction.message.interaction?.user?.id - ) { - const notAllowedEmbed = new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ Not Allowed") - .setDescription("Only the targeted user can respond to this Truth or Dare!") - .setTimestamp(); - - return interaction.reply({ - embeds: [notAllowedEmbed], - flags: MessageFlags.Ephemeral, - }); - } - - try { - const db = await Database.getInstance(); - - // Check cooldown - const config = await db.TruthOrDareConfig.findOne({ - guildId: interaction.guild.id, - }); - const cooldownTime = (config?.cooldownTime || 5) * 1000; - - // Simple cooldown check using user interaction timestamp - const lastUsed = client.truthOrDareCooldowns?.get(interaction.user.id) || 0; - const now = Date.now(); - - if (now - lastUsed < cooldownTime) { - const remainingTime = Math.ceil((cooldownTime - (now - lastUsed)) / 1000); - const cooldownEmbed = new EmbedBuilder() - .setColor("#ffaa00") - .setTitle("⏱️ Cooldown Active") - .setDescription( - `Please wait ${remainingTime} more seconds before using Truth or Dare again.` - ) - .setTimestamp(); - - return interaction.reply({ - embeds: [cooldownEmbed], - flags: MessageFlags.Ephemeral, - }); - } - - // Set cooldown - if (!client.truthOrDareCooldowns) client.truthOrDareCooldowns = new Map(); - client.truthOrDareCooldowns.set(interaction.user.id, now); - - // Determine the type (handle random selection) - let type = action; - if (action === "random") { - type = Math.random() < 0.5 ? "truth" : "dare"; - } - - // Get random truth or dare - const question = await getRandomTruthOrDare(db, interaction.guild.id, type); - - const resultEmbed = new EmbedBuilder() - .setColor(type === "truth" ? "#4287f5" : "#ff4757") - .setTitle(type === "truth" ? "💭 Truth Question" : "🎯 Dare Challenge") - .setDescription(question) - .addFields( - { - name: "For", - value: `<@${targetUserId}>`, - inline: true, - }, - { - name: "Type", - value: - action === "random" - ? `🎲 Random (${type})` - : type.charAt(0).toUpperCase() + type.slice(1), - inline: true, - } - ) - .setThumbnail( - interaction.guild.members.cache.get(targetUserId)?.displayAvatarURL() || null - ) - .setFooter({ - text: `Have fun and stay safe! • ${ - type === "truth" ? "Be honest" : "Be careful" - }`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - await interaction.update({ embeds: [resultEmbed], components: [] }); - } catch (error) { - console.error("Error handling Truth or Dare button:", error); - - const errorEmbed = new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ Error") - .setDescription("An error occurred while processing your Truth or Dare request.") - .setTimestamp(); - - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ - embeds: [errorEmbed], - flags: MessageFlags.Ephemeral, - }); - } else { - await interaction.reply({ - embeds: [errorEmbed], - flags: MessageFlags.Ephemeral, - }); - } - } + const { EmbedBuilder, MessageFlags } = require("discord.js"); + const Database = require("../utils/database"); + const { + getRandomTruthOrDare, + } = require("../commands/truth-or-dare/truthordare"); + + const customIdParts = interaction.customId.split("_"); + const action = customIdParts[1]; // "truth", "dare", "random", "rules", "stats" + const targetUserId = customIdParts[2]; + + // Handle non-game actions first + if (action === "rules") { + const rulesEmbed = new EmbedBuilder() + .setColor("#4287f5") + .setTitle("📋 Truth or Dare Rules") + .setDescription("Keep it fun and respectful for everyone!") + .addFields( + { + name: "✅ Do's", + value: + "• Be honest with truths\n• Complete dares safely\n• Respect others' boundaries\n• Keep it appropriate for the server", + inline: false, + }, + { + name: "❌ Don'ts", + value: + "• Share inappropriate content\n• Do anything harmful or illegal\n• Force participation\n• Break server rules", + inline: false, + }, + { + name: "🛡️ Safety First", + value: + "If you're uncomfortable with a truth or dare, you can always skip it. Your safety and comfort matter most!", + inline: false, + }, + ) + .setFooter({ text: "Have fun and play responsibly!" }); + + return interaction.reply({ + embeds: [rulesEmbed], + flags: MessageFlags.Ephemeral, + }); + } + + if (action === "stats") { + try { + const db = await Database.getInstance(); + const config = await db.TruthOrDareConfig.findOne({ + guildId: interaction.guild.id, + }); + + const customTruths = config?.customTruths?.length || 0; + const customDares = config?.customDares?.length || 0; + const totalCustom = customTruths + customDares; + + const statsEmbed = new EmbedBuilder() + .setColor("#00ff88") + .setTitle("📊 Truth or Dare Server Stats") + .setDescription(`Statistics for **${interaction.guild.name}**`) + .addFields( + { + name: "💭 Custom Truths", + value: `${customTruths}`, + inline: true, + }, + { + name: "🎯 Custom Dares", + value: `${customDares}`, + inline: true, + }, + { + name: "🎮 Total Custom Content", + value: `${totalCustom}`, + inline: true, + }, + { + name: "📚 Default Content", + value: "15 truths, 15 dares", + inline: true, + }, + { + name: "🎲 Total Available", + value: `${30 + totalCustom} questions/dares`, + inline: true, + }, + { + name: "⚙️ Status", + value: config?.enabled !== false ? "✅ Enabled" : "❌ Disabled", + inline: true, + }, + ) + .setFooter({ text: "Use /truthordare add to contribute content!" }); + + return interaction.reply({ + embeds: [statsEmbed], + flags: MessageFlags.Ephemeral, + }); + } catch (error) { + console.error("Error fetching ToD stats:", error); + return interaction.reply({ + content: "❌ Failed to fetch server stats.", + flags: MessageFlags.Ephemeral, + }); + } + } + + // Check if the user clicking is the target user or the original user + if ( + interaction.user.id !== targetUserId && + interaction.user.id !== interaction.message.interaction?.user?.id + ) { + const notAllowedEmbed = new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ Not Allowed") + .setDescription( + "Only the targeted user can respond to this Truth or Dare!", + ) + .setTimestamp(); + + return interaction.reply({ + embeds: [notAllowedEmbed], + flags: MessageFlags.Ephemeral, + }); + } + + try { + const db = await Database.getInstance(); + + // Check cooldown + const config = await db.TruthOrDareConfig.findOne({ + guildId: interaction.guild.id, + }); + const cooldownTime = (config?.cooldownTime || 5) * 1000; + + // Simple cooldown check using user interaction timestamp + const lastUsed = client.truthOrDareCooldowns?.get(interaction.user.id) || 0; + const now = Date.now(); + + if (now - lastUsed < cooldownTime) { + const remainingTime = Math.ceil((cooldownTime - (now - lastUsed)) / 1000); + const cooldownEmbed = new EmbedBuilder() + .setColor("#ffaa00") + .setTitle("⏱️ Cooldown Active") + .setDescription( + `Please wait ${remainingTime} more seconds before using Truth or Dare again.`, + ) + .setTimestamp(); + + return interaction.reply({ + embeds: [cooldownEmbed], + flags: MessageFlags.Ephemeral, + }); + } + + // Set cooldown + if (!client.truthOrDareCooldowns) client.truthOrDareCooldowns = new Map(); + client.truthOrDareCooldowns.set(interaction.user.id, now); + + // Determine the type (handle random selection) + let type = action; + if (action === "random") { + type = Math.random() < 0.5 ? "truth" : "dare"; + } + + // Get random truth or dare + const question = await getRandomTruthOrDare(db, interaction.guild.id, type); + + const resultEmbed = new EmbedBuilder() + .setColor(type === "truth" ? "#4287f5" : "#ff4757") + .setTitle(type === "truth" ? "💭 Truth Question" : "🎯 Dare Challenge") + .setDescription(question) + .addFields( + { + name: "For", + value: `<@${targetUserId}>`, + inline: true, + }, + { + name: "Type", + value: + action === "random" + ? `🎲 Random (${type})` + : type.charAt(0).toUpperCase() + type.slice(1), + inline: true, + }, + ) + .setThumbnail( + interaction.guild.members.cache.get(targetUserId)?.displayAvatarURL() || + null, + ) + .setFooter({ + text: `Have fun and stay safe! • ${ + type === "truth" ? "Be honest" : "Be careful" + }`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await interaction.update({ embeds: [resultEmbed], components: [] }); + } catch (error) { + console.error("Error handling Truth or Dare button:", error); + + const errorEmbed = new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ Error") + .setDescription( + "An error occurred while processing your Truth or Dare request.", + ) + .setTimestamp(); + + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + embeds: [errorEmbed], + flags: MessageFlags.Ephemeral, + }); + } else { + await interaction.reply({ + embeds: [errorEmbed], + flags: MessageFlags.Ephemeral, + }); + } + } } // 🤖 Handle AI ask again interaction async function handleAIAskAgain(interaction, client) { - const { - ModalBuilder, - TextInputBuilder, - TextInputStyle, - ActionRowBuilder, - } = require("discord.js"); - - const modal = new ModalBuilder() - .setCustomId("ai_ask_modal") - .setTitle("🤖 Ask AI Assistant Anything"); - - const questionInput = new TextInputBuilder() - .setCustomId("ai_question_input") - .setLabel("Your Question") - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder("Ask me anything! I'm here to help.") - .setRequired(true) - .setMaxLength(1000); - - const row = new ActionRowBuilder().addComponents(questionInput); - modal.addComponents(row); - - await interaction.showModal(modal); + const { + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, + } = require("discord.js"); + + const modal = new ModalBuilder() + .setCustomId("ai_ask_modal") + .setTitle("🤖 Ask AI Assistant Anything"); + + const questionInput = new TextInputBuilder() + .setCustomId("ai_question_input") + .setLabel("Your Question") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Ask me anything! I'm here to help.") + .setRequired(true) + .setMaxLength(1000); + + const row = new ActionRowBuilder().addComponents(questionInput); + modal.addComponents(row); + + await interaction.showModal(modal); } // ⭐ Handle AI feedback button async function handleAIFeedback(interaction, client) { - const { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - } = require("discord.js"); - - const feedbackEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle("⭐ Rate AI Response") - .setDescription("How was the AI's response? Your feedback helps us improve!") - .addFields({ - name: "🎯 What we track", - value: "• Response helpfulness\n• Accuracy\n• Clarity\n• Overall satisfaction", - inline: false, - }) - .setFooter({ text: "Your feedback is anonymous and helps improve the AI" }); - - const feedbackRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`ai_rate_excellent_${interaction.user.id}`) - .setLabel("Excellent") - .setStyle(ButtonStyle.Success) - .setEmoji("⭐"), - new ButtonBuilder() - .setCustomId(`ai_rate_good_${interaction.user.id}`) - .setLabel("Good") - .setStyle(ButtonStyle.Primary) - .setEmoji("👍"), - new ButtonBuilder() - .setCustomId(`ai_rate_poor_${interaction.user.id}`) - .setLabel("Poor") - .setStyle(ButtonStyle.Danger) - .setEmoji("👎") - ); - - await interaction.reply({ - embeds: [feedbackEmbed], - components: [feedbackRow], - flags: 64, - }); + const { + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + } = require("discord.js"); + + const feedbackEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle("⭐ Rate AI Response") + .setDescription( + "How was the AI's response? Your feedback helps us improve!", + ) + .addFields({ + name: "🎯 What we track", + value: + "• Response helpfulness\n• Accuracy\n• Clarity\n• Overall satisfaction", + inline: false, + }) + .setFooter({ text: "Your feedback is anonymous and helps improve the AI" }); + + const feedbackRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`ai_rate_excellent_${interaction.user.id}`) + .setLabel("Excellent") + .setStyle(ButtonStyle.Success) + .setEmoji("⭐"), + new ButtonBuilder() + .setCustomId(`ai_rate_good_${interaction.user.id}`) + .setLabel("Good") + .setStyle(ButtonStyle.Primary) + .setEmoji("👍"), + new ButtonBuilder() + .setCustomId(`ai_rate_poor_${interaction.user.id}`) + .setLabel("Poor") + .setStyle(ButtonStyle.Danger) + .setEmoji("👎"), + ); + + await interaction.reply({ + embeds: [feedbackEmbed], + components: [feedbackRow], + flags: 64, + }); } // ⭐ Handle AI rating async function handleAIRating(interaction, client) { - const { EmbedBuilder } = require("discord.js"); - - const rating = interaction.customId.split("_")[2]; // excellent, good, or poor - - const ratingEmojis = { - excellent: "⭐⭐⭐⭐⭐", - good: "👍👍👍", - poor: "👎", - }; - - const ratingMessages = { - excellent: "Thank you! We're glad the AI was very helpful!", - good: "Thanks for the feedback! We'll keep improving.", - poor: "Thanks for letting us know. We'll work on improving the AI responses.", - }; - - const ratingEmbed = new EmbedBuilder() - .setColor( - rating === "excellent" - ? client.colors.success - : rating === "good" - ? client.colors.primary - : client.colors.warning - ) - .setTitle(`${ratingEmojis[rating]} Rating Submitted`) - .setDescription(ratingMessages[rating]) - .setFooter({ text: "Your feedback helps us improve the AI assistant!" }) - .setTimestamp(); - - // Log the feedback (you could save this to database for analytics) - console.log( - `AI Feedback: ${rating} from ${interaction.user.tag} in ${interaction.guild.name}` - ); - - await interaction.update({ - embeds: [ratingEmbed], - components: [], - }); + const { EmbedBuilder } = require("discord.js"); + + const rating = interaction.customId.split("_")[2]; // excellent, good, or poor + + const ratingEmojis = { + excellent: "⭐⭐⭐⭐⭐", + good: "👍👍👍", + poor: "👎", + }; + + const ratingMessages = { + excellent: "Thank you! We're glad the AI was very helpful!", + good: "Thanks for the feedback! We'll keep improving.", + poor: "Thanks for letting us know. We'll work on improving the AI responses.", + }; + + const ratingEmbed = new EmbedBuilder() + .setColor( + rating === "excellent" + ? client.colors.success + : rating === "good" + ? client.colors.primary + : client.colors.warning, + ) + .setTitle(`${ratingEmojis[rating]} Rating Submitted`) + .setDescription(ratingMessages[rating]) + .setFooter({ text: "Your feedback helps us improve the AI assistant!" }) + .setTimestamp(); + + // Log the feedback (you could save this to database for analytics) + console.log( + `AI Feedback: ${rating} from ${interaction.user.tag} in ${interaction.guild.name}`, + ); + + await interaction.update({ + embeds: [ratingEmbed], + components: [], + }); } // 🤖 Handle AI ask modal submission async function handleAIAskModal(interaction, client) { - const { GoogleGenAI } = require("@google/genai"); - const { - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - } = require("discord.js"); - const Database = require("@adb/server/utils/database"); - const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); - - const history = []; - - const question = interaction.fields.getTextInputValue("ai_question_input"); - - // Check rate limiting (5 requests per hour - same as slash command) - const db = await Database.getInstance(); - const rateLimit = await db.checkRateLimit( - interaction.user.id, - interaction.guild.id, - 5, // 5 requests - 3600000 // 1 hour - ); - - if (!rateLimit.allowed) { - const resetTime = Math.floor(rateLimit.resetTime.getTime() / 1000); - const rateLimitEmbed = new EmbedBuilder() - .setColor("#ff9900") - .setTitle("⏱️ Rate Limited") - .setDescription( - `You've reached the AI request limit (5 per hour).\n\n**Reset:** ` - ) - .setFooter({ - text: "Rate limiting helps manage API costs and ensures fair usage.", - }) - .setTimestamp(); - - return interaction.reply({ embeds: [rateLimitEmbed], ephemeral: true }); - } - - await interaction.deferReply(); - - try { - // Initialize Gemini AI - const systemPrompt = `You are a helpful AI assistant named Vaish. Give concise answers of the questions, queries of the user`; - - history.push({ - role: "user", - parts: [{ text: question }], - }); - - const response = await ai.models.generateContent({ - model: "gemini-2.5-flash", - contents: history, - config: { - systemInstruction: systemPrompt, - }, - }); - - // Truncate if too long - const truncatedResponse = - response.text.length > 1500 - ? response.text.substring(0, 1500) + "..." - : response.text; - - const aiEmbed = new EmbedBuilder() - .setColor("#0099ff") - .setTitle("🤖 AI Assistant") - .setDescription(truncatedResponse) - .addFields({ - name: "❓ Your Question", - value: question.length > 200 ? question.substring(0, 200) + "..." : question, - inline: false, - }) - .setFooter({ - text: `Asked by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - // Add modern UI components for user interaction - const actionRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`ai_ask_again_${interaction.user.id}`) - .setLabel("Ask Another") - .setStyle(ButtonStyle.Primary) - .setEmoji("🔄"), - new ButtonBuilder() - .setCustomId(`ai_rate_${interaction.user.id}`) - .setLabel("Rate Response") - .setStyle(ButtonStyle.Secondary) - .setEmoji("⭐") - ); - - await interaction.editReply({ - embeds: [aiEmbed], - components: [actionRow], - }); - } catch (error) { - console.error("AI generation error:", error); - - let errorEmbed; - - // Handle specific API errors - if (error.message && error.message.includes("429")) { - // Gemini API quota exceeded - errorEmbed = new EmbedBuilder() - .setColor("#ff6b6b") - .setTitle("🚫 API Quota Exceeded") - .setDescription( - "The AI service is currently at its daily quota limit. This usually resets at midnight UTC.\n\n" + - "**What you can do:**\n" + - "• Try again later (quota resets daily)\n" + - "• Use shorter, simpler questions\n" + - "• Contact server admins if this persists\n\n" + - "**Alternative:** Try using the `/help` command for basic information!" - ) - .setFooter({ - text: "We're using Google's free tier - quota limits help keep the bot free!", - }) - .setTimestamp(); - } else if ( - error.status === 429 || - (error.response && error.response.status === 429) - ) { - // Another way quota exceeded error might appear - errorEmbed = new EmbedBuilder() - .setColor("#ff6b6b") - .setTitle("🚫 API Quota Exceeded") - .setDescription( - "The AI service is currently at its daily quota limit. This usually resets at midnight UTC.\n\n" + - "**What you can do:**\n" + - "• Try again later (quota resets daily)\n" + - "• Use shorter, simpler questions\n" + - "• Contact server admins if this persists\n\n" + - "**Alternative:** Try using the `/help` command for basic information!" - ) - .setFooter({ - text: "We're using Google's free tier - quota limits help keep the bot free!", - }) - .setTimestamp(); - } else if (error.message && error.message.includes("SAFETY")) { - // Content safety filter triggered - errorEmbed = new EmbedBuilder() - .setColor("#ff9900") - .setTitle("🛡️ Content Safety Filter") - .setDescription( - "Your question was flagged by the AI's safety filters. Please try rephrasing your question in a different way." - ) - .setTimestamp(); - } else { - // Generic error - errorEmbed = new EmbedBuilder() - .setColor("#ff0000") - .setTitle("❌ AI Error") - .setDescription( - "Sorry, I encountered an error processing your question. Please try again in a moment." - ) - .setTimestamp(); - } - - await interaction.editReply({ embeds: [errorEmbed] }); - } + const { GoogleGenAI } = require("@google/genai"); + const { + EmbedBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + } = require("discord.js"); + const Database = require("../utils/database"); + const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + + const history = []; + + const question = interaction.fields.getTextInputValue("ai_question_input"); + + // Check rate limiting (5 requests per hour - same as slash command) + const db = await Database.getInstance(); + const rateLimit = await db.checkRateLimit( + interaction.user.id, + interaction.guild.id, + 5, // 5 requests + 3600000, // 1 hour + ); + + if (!rateLimit.allowed) { + const resetTime = Math.floor(rateLimit.resetTime.getTime() / 1000); + const rateLimitEmbed = new EmbedBuilder() + .setColor("#ff9900") + .setTitle("⏱️ Rate Limited") + .setDescription( + `You've reached the AI request limit (5 per hour).\n\n**Reset:** `, + ) + .setFooter({ + text: "Rate limiting helps manage API costs and ensures fair usage.", + }) + .setTimestamp(); + + return interaction.reply({ embeds: [rateLimitEmbed], ephemeral: true }); + } + + await interaction.deferReply(); + + try { + // Initialize Gemini AI + const systemPrompt = `You are a helpful AI assistant named Vaish. Give concise answers of the questions, queries of the user`; + + history.push({ + role: "user", + parts: [{ text: question }], + }); + + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: history, + config: { + systemInstruction: systemPrompt, + }, + }); + + // Truncate if too long + const truncatedResponse = + response.text.length > 1500 + ? response.text.substring(0, 1500) + "..." + : response.text; + + const aiEmbed = new EmbedBuilder() + .setColor("#0099ff") + .setTitle("🤖 AI Assistant") + .setDescription(truncatedResponse) + .addFields({ + name: "❓ Your Question", + value: + question.length > 200 ? question.substring(0, 200) + "..." : question, + inline: false, + }) + .setFooter({ + text: `Asked by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + // Add modern UI components for user interaction + const actionRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`ai_ask_again_${interaction.user.id}`) + .setLabel("Ask Another") + .setStyle(ButtonStyle.Primary) + .setEmoji("🔄"), + new ButtonBuilder() + .setCustomId(`ai_rate_${interaction.user.id}`) + .setLabel("Rate Response") + .setStyle(ButtonStyle.Secondary) + .setEmoji("⭐"), + ); + + await interaction.editReply({ + embeds: [aiEmbed], + components: [actionRow], + }); + } catch (error) { + console.error("AI generation error:", error); + + let errorEmbed; + + // Handle specific API errors + if (error.message && error.message.includes("429")) { + // Gemini API quota exceeded + errorEmbed = new EmbedBuilder() + .setColor("#ff6b6b") + .setTitle("🚫 API Quota Exceeded") + .setDescription( + "The AI service is currently at its daily quota limit. This usually resets at midnight UTC.\n\n" + + "**What you can do:**\n" + + "• Try again later (quota resets daily)\n" + + "• Use shorter, simpler questions\n" + + "• Contact server admins if this persists\n\n" + + "**Alternative:** Try using the `/help` command for basic information!", + ) + .setFooter({ + text: "We're using Google's free tier - quota limits help keep the bot free!", + }) + .setTimestamp(); + } else if ( + error.status === 429 || + (error.response && error.response.status === 429) + ) { + // Another way quota exceeded error might appear + errorEmbed = new EmbedBuilder() + .setColor("#ff6b6b") + .setTitle("🚫 API Quota Exceeded") + .setDescription( + "The AI service is currently at its daily quota limit. This usually resets at midnight UTC.\n\n" + + "**What you can do:**\n" + + "• Try again later (quota resets daily)\n" + + "• Use shorter, simpler questions\n" + + "• Contact server admins if this persists\n\n" + + "**Alternative:** Try using the `/help` command for basic information!", + ) + .setFooter({ + text: "We're using Google's free tier - quota limits help keep the bot free!", + }) + .setTimestamp(); + } else if (error.message && error.message.includes("SAFETY")) { + // Content safety filter triggered + errorEmbed = new EmbedBuilder() + .setColor("#ff9900") + .setTitle("🛡️ Content Safety Filter") + .setDescription( + "Your question was flagged by the AI's safety filters. Please try rephrasing your question in a different way.", + ) + .setTimestamp(); + } else { + // Generic error + errorEmbed = new EmbedBuilder() + .setColor("#ff0000") + .setTitle("❌ AI Error") + .setDescription( + "Sorry, I encountered an error processing your question. Please try again in a moment.", + ) + .setTimestamp(); + } + + await interaction.editReply({ embeds: [errorEmbed] }); + } } // ⏰ Handle reminder buttons async function handleReminderButtons(interaction, client) { - const { EmbedBuilder } = require("discord.js"); - - const action = interaction.customId.split("_")[1]; // info, tips, snooze, done - - switch (action) { - case "info": - const infoEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle("📋 Reminder Information") - .setDescription("Here's everything you need to know about reminders:") - .addFields( - { - name: "📬 Delivery Method", - value: - "• Direct Messages (preferred)\n• Channel fallback if DMs fail\n• Make sure your DMs are open", - inline: false, - }, - { - name: "⏱️ Time Formats", - value: - "• `30s` - 30 seconds\n• `5m` - 5 minutes\n• `2h` - 2 hours\n• `1d` - 1 day\n• `1w` - 1 week", - inline: true, - }, - { - name: "🛡️ Limits", - value: "• Minimum: 30 seconds\n• Maximum: 1 year\n• Cooldown: 5 seconds", - inline: true, - } - ) - .setFooter({ text: "Use reminders responsibly!" }); - - await interaction.reply({ embeds: [infoEmbed], flags: 64 }); - break; - - case "tips": - const tipsEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("💡 Reminder Tips & Best Practices") - .setDescription("Get the most out of your reminders:") - .addFields( - { - name: "✅ Do's", - value: - "• Be specific in your reminder text\n• Include context for future you\n• Use appropriate time frames\n• Enable DMs for reliable delivery", - inline: false, - }, - { - name: "❌ Don'ts", - value: - "• Don't spam short reminders\n• Avoid setting too many at once\n• Don't rely on bot for critical tasks\n• Don't use offensive language", - inline: false, - }, - { - name: "🔥 Pro Tips", - value: - "• Include action items: 'Call John about project'\n• Use time zones: 'Meeting at 3pm EST'\n• Be descriptive: 'Take medicine after lunch'", - inline: false, - } - ) - .setFooter({ text: "Happy reminder setting!" }); - - await interaction.reply({ embeds: [tipsEmbed], flags: 64 }); - break; - - case "snooze": - // Set a 5-minute snooze - setTimeout(async () => { - try { - const snoozeEmbed = new EmbedBuilder() - .setColor(client.colors.warning) - .setTitle("💤 Snooze Alert!") - .setDescription("Your snoozed reminder is here!") - .addFields({ - name: "⏰ Snoozed", - value: "5 minutes ago", - inline: true, - }) - .setFooter({ text: "This was a snoozed reminder" }) - .setTimestamp(); - - await interaction.user.send({ embeds: [snoozeEmbed] }); - } catch (error) { - console.error("Failed to send snooze reminder:", error); - } - }, 5 * 60 * 1000); // 5 minutes - - const snoozeConfirmEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("💤 Reminder Snoozed") - .setDescription("I'll remind you again in 5 minutes!") - .setTimestamp(); - - await interaction.update({ - embeds: [snoozeConfirmEmbed], - components: [], - }); - break; - - case "done": - const doneEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ Reminder Completed") - .setDescription("Great job! Reminder marked as done.") - .setFooter({ text: "Thanks for staying organized!" }) - .setTimestamp(); - - await interaction.update({ embeds: [doneEmbed], components: [] }); - break; - } + const { EmbedBuilder } = require("discord.js"); + + const action = interaction.customId.split("_")[1]; // info, tips, snooze, done + + switch (action) { + case "info": + const infoEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle("📋 Reminder Information") + .setDescription("Here's everything you need to know about reminders:") + .addFields( + { + name: "📬 Delivery Method", + value: + "• Direct Messages (preferred)\n• Channel fallback if DMs fail\n• Make sure your DMs are open", + inline: false, + }, + { + name: "⏱️ Time Formats", + value: + "• `30s` - 30 seconds\n• `5m` - 5 minutes\n• `2h` - 2 hours\n• `1d` - 1 day\n• `1w` - 1 week", + inline: true, + }, + { + name: "🛡️ Limits", + value: + "• Minimum: 30 seconds\n• Maximum: 1 year\n• Cooldown: 5 seconds", + inline: true, + }, + ) + .setFooter({ text: "Use reminders responsibly!" }); + + await interaction.reply({ embeds: [infoEmbed], flags: 64 }); + break; + + case "tips": + const tipsEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("💡 Reminder Tips & Best Practices") + .setDescription("Get the most out of your reminders:") + .addFields( + { + name: "✅ Do's", + value: + "• Be specific in your reminder text\n• Include context for future you\n• Use appropriate time frames\n• Enable DMs for reliable delivery", + inline: false, + }, + { + name: "❌ Don'ts", + value: + "• Don't spam short reminders\n• Avoid setting too many at once\n• Don't rely on bot for critical tasks\n• Don't use offensive language", + inline: false, + }, + { + name: "🔥 Pro Tips", + value: + "• Include action items: 'Call John about project'\n• Use time zones: 'Meeting at 3pm EST'\n• Be descriptive: 'Take medicine after lunch'", + inline: false, + }, + ) + .setFooter({ text: "Happy reminder setting!" }); + + await interaction.reply({ embeds: [tipsEmbed], flags: 64 }); + break; + + case "snooze": + // Set a 5-minute snooze + setTimeout( + async () => { + try { + const snoozeEmbed = new EmbedBuilder() + .setColor(client.colors.warning) + .setTitle("💤 Snooze Alert!") + .setDescription("Your snoozed reminder is here!") + .addFields({ + name: "⏰ Snoozed", + value: "5 minutes ago", + inline: true, + }) + .setFooter({ text: "This was a snoozed reminder" }) + .setTimestamp(); + + await interaction.user.send({ embeds: [snoozeEmbed] }); + } catch (error) { + console.error("Failed to send snooze reminder:", error); + } + }, + 5 * 60 * 1000, + ); // 5 minutes + + const snoozeConfirmEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("💤 Reminder Snoozed") + .setDescription("I'll remind you again in 5 minutes!") + .setTimestamp(); + + await interaction.update({ + embeds: [snoozeConfirmEmbed], + components: [], + }); + break; + + case "done": + const doneEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ Reminder Completed") + .setDescription("Great job! Reminder marked as done.") + .setFooter({ text: "Thanks for staying organized!" }) + .setTimestamp(); + + await interaction.update({ embeds: [doneEmbed], components: [] }); + break; + } } // 🎫 Handle ticket system button interactions async function handleTicketButtons(interaction, client) { - const Database = require("@adb/server/utils/database"); - const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); - - const db = await Database.getInstance(); - const customId = interaction.customId; - - try { - if (customId.startsWith("ticket_claim_")) { - const ticketId = customId.split("_")[2]; - - // Check if user is a moderator - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - return await interaction.reply({ - content: "❌ Only moderators can claim tickets.", - ephemeral: true, - }); - } - - // Update ticket in database - await db.updateTicket(ticketId, { - moderatorId: interaction.user.id, - status: "in_progress", - }); - - // Update embed - const ticket = await db.getTicketById(ticketId); - const embed = EmbedBuilder.from(interaction.message.embeds[0]) - .addFields({ - name: "👨‍💼 Claimed by", - value: `${interaction.user}`, - inline: true, - }) - .setColor("#FFA500"); - - // Update buttons - const newButtons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(`ticket_unclaim_${ticketId}`) - .setLabel("❌ Unclaim") - .setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId(`ticket_close_${ticketId}`) - .setLabel("🔒 Close Ticket") - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setCustomId(`ticket_priority_${ticketId}`) - .setLabel("📊 Change Priority") - .setStyle(ButtonStyle.Secondary) - ); - - await interaction.update({ - embeds: [embed], - components: [newButtons], - }); - - await interaction.followUp({ - content: `✅ ${interaction.user} has claimed this ticket and will assist you.`, - ephemeral: false, - }); - } else if (customId.startsWith("ticket_close_")) { - const ticketId = customId.split("_")[2]; - - // Check if user is a moderator or ticket creator - const ticket = await db.getTicketById(ticketId); - const isMod = isModeratorOrOwner(interaction.member, interaction.guild); - const isCreator = ticket.userId === interaction.user.id; - - if (!isMod && !isCreator) { - return await interaction.reply({ - content: "❌ Only moderators or the ticket creator can close tickets.", - ephemeral: true, - }); - } - - // Show confirmation modal - const modal = new ModalBuilder() - .setCustomId(`close_ticket_modal_${ticketId}`) - .setTitle("Close Ticket"); - - const reasonInput = new TextInputBuilder() - .setCustomId("close_reason") - .setLabel("Reason for closing (optional)") - .setStyle(TextInputStyle.Paragraph) - .setRequired(false) - .setMaxLength(500); - - modal.addComponents(new ActionRowBuilder().addComponents(reasonInput)); - - await interaction.showModal(modal); - } else if (customId.startsWith("ticket_priority_")) { - const ticketId = customId.split("_")[2]; - - // Check if user is a moderator - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - return await interaction.reply({ - content: "❌ Only moderators can change ticket priority.", - ephemeral: true, - }); - } - - // Show priority selection - const priorityRow = new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId(`priority_select_${ticketId}`) - .setPlaceholder("Select new priority level") - .addOptions([ - { - label: "🔴 High Priority", - value: "high", - description: "Urgent issues requiring immediate attention", - }, - { - label: "🟡 Medium Priority", - value: "medium", - description: "Standard issues with normal response time", - }, - { - label: "🟢 Low Priority", - value: "low", - description: "Minor issues with flexible response time", - }, - ]) - ); - - await interaction.reply({ - content: "Select the new priority level:", - components: [priorityRow], - ephemeral: true, - }); - } - } catch (error) { - console.error("Error handling ticket button:", error); - await interaction.reply({ - content: "❌ An error occurred while processing your request.", - ephemeral: true, - }); - } + const Database = require("../utils/database"); + const { isModeratorOrOwner } = require("../utils/moderation"); + + const db = await Database.getInstance(); + const customId = interaction.customId; + + try { + if (customId.startsWith("ticket_claim_")) { + const ticketId = customId.split("_")[2]; + + // Check if user is a moderator + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + return await interaction.reply({ + content: "❌ Only moderators can claim tickets.", + ephemeral: true, + }); + } + + // Update ticket in database + await db.updateTicket(ticketId, { + moderatorId: interaction.user.id, + status: "in_progress", + }); + + // Update embed + const ticket = await db.getTicketById(ticketId); + const embed = EmbedBuilder.from(interaction.message.embeds[0]) + .addFields({ + name: "👨‍💼 Claimed by", + value: `${interaction.user}`, + inline: true, + }) + .setColor("#FFA500"); + + // Update buttons + const newButtons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`ticket_unclaim_${ticketId}`) + .setLabel("❌ Unclaim") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`ticket_close_${ticketId}`) + .setLabel("🔒 Close Ticket") + .setStyle(ButtonStyle.Danger), + new ButtonBuilder() + .setCustomId(`ticket_priority_${ticketId}`) + .setLabel("📊 Change Priority") + .setStyle(ButtonStyle.Secondary), + ); + + await interaction.update({ + embeds: [embed], + components: [newButtons], + }); + + await interaction.followUp({ + content: `✅ ${interaction.user} has claimed this ticket and will assist you.`, + ephemeral: false, + }); + } else if (customId.startsWith("ticket_close_")) { + const ticketId = customId.split("_")[2]; + + // Check if user is a moderator or ticket creator + const ticket = await db.getTicketById(ticketId); + const isMod = isModeratorOrOwner(interaction.member, interaction.guild); + const isCreator = ticket.userId === interaction.user.id; + + if (!isMod && !isCreator) { + return await interaction.reply({ + content: + "❌ Only moderators or the ticket creator can close tickets.", + ephemeral: true, + }); + } + + // Show confirmation modal + const modal = new ModalBuilder() + .setCustomId(`close_ticket_modal_${ticketId}`) + .setTitle("Close Ticket"); + + const reasonInput = new TextInputBuilder() + .setCustomId("close_reason") + .setLabel("Reason for closing (optional)") + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + .setMaxLength(500); + + modal.addComponents(new ActionRowBuilder().addComponents(reasonInput)); + + await interaction.showModal(modal); + } else if (customId.startsWith("ticket_priority_")) { + const ticketId = customId.split("_")[2]; + + // Check if user is a moderator + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + return await interaction.reply({ + content: "❌ Only moderators can change ticket priority.", + ephemeral: true, + }); + } + + // Show priority selection + const priorityRow = new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`priority_select_${ticketId}`) + .setPlaceholder("Select new priority level") + .addOptions([ + { + label: "🔴 High Priority", + value: "high", + description: "Urgent issues requiring immediate attention", + }, + { + label: "🟡 Medium Priority", + value: "medium", + description: "Standard issues with normal response time", + }, + { + label: "🟢 Low Priority", + value: "low", + description: "Minor issues with flexible response time", + }, + ]), + ); + + await interaction.reply({ + content: "Select the new priority level:", + components: [priorityRow], + ephemeral: true, + }); + } + } catch (error) { + console.error("Error handling ticket button:", error); + await interaction.reply({ + content: "❌ An error occurred while processing your request.", + ephemeral: true, + }); + } } // 🔒 Handle close ticket modal submission async function handleCloseTicketModal(interaction, client) { - const Database = require("@adb/server/utils/database"); - const db = await Database.getInstance(); - - const ticketId = interaction.customId.split("_")[3]; - const closeReason = - interaction.fields.getTextInputValue("close_reason") || "No reason provided"; - - try { - // Get ticket data - const ticket = await db.getTicketById(ticketId); - if (!ticket) { - return await interaction.reply({ - content: "❌ Ticket not found.", - ephemeral: true, - }); - } - - // Update ticket status to closed - await db.updateTicket(ticketId, { - status: "closed", - closedAt: new Date(), - closedBy: interaction.user.id, - closeReason: closeReason, - }); - - // Create closing embed - const closeEmbed = new EmbedBuilder() - .setColor("#FF0000") - .setTitle("🔒 Ticket Closed") - .setDescription("This ticket has been closed.") - .addFields( - { - name: "👤 Closed by", - value: `${interaction.user}`, - inline: true, - }, - { - name: "📅 Closed at", - value: ``, - inline: true, - }, - { - name: "📝 Reason", - value: closeReason, - inline: false, - } - ) - .setFooter({ text: "This channel will be deleted in 30 seconds." }) - .setTimestamp(); - - // Send closing message - await interaction.reply({ - embeds: [closeEmbed], - }); - - // Delete the channel after 30 seconds - setTimeout(async () => { - try { - if (interaction.channel && interaction.channel.deletable) { - await interaction.channel.delete(); - } - } catch (error) { - console.error("Error deleting ticket channel:", error); - } - }, 30000); - - console.log( - `🔒 Ticket #${ticket.ticketId || ticketId} closed by ${interaction.user.tag}` - ); - } catch (error) { - console.error("Error closing ticket:", error); - await interaction.reply({ - content: "❌ An error occurred while closing the ticket.", - ephemeral: true, - }); - } + const Database = require("../utils/database"); + const db = await Database.getInstance(); + + const ticketId = interaction.customId.split("_")[3]; + const closeReason = + interaction.fields.getTextInputValue("close_reason") || + "No reason provided"; + + try { + // Get ticket data + const ticket = await db.getTicketById(ticketId); + if (!ticket) { + return await interaction.reply({ + content: "❌ Ticket not found.", + ephemeral: true, + }); + } + + // Update ticket status to closed + await db.updateTicket(ticketId, { + status: "closed", + closedAt: new Date(), + closedBy: interaction.user.id, + closeReason: closeReason, + }); + + // Create closing embed + const closeEmbed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("🔒 Ticket Closed") + .setDescription("This ticket has been closed.") + .addFields( + { + name: "👤 Closed by", + value: `${interaction.user}`, + inline: true, + }, + { + name: "📅 Closed at", + value: ``, + inline: true, + }, + { + name: "📝 Reason", + value: closeReason, + inline: false, + }, + ) + .setFooter({ text: "This channel will be deleted in 30 seconds." }) + .setTimestamp(); + + // Send closing message + await interaction.reply({ + embeds: [closeEmbed], + }); + + // Delete the channel after 30 seconds + setTimeout(async () => { + try { + if (interaction.channel && interaction.channel.deletable) { + await interaction.channel.delete(); + } + } catch (error) { + console.error("Error deleting ticket channel:", error); + } + }, 30000); + + console.log( + `🔒 Ticket #${ticket.ticketId || ticketId} closed by ${interaction.user.tag}`, + ); + } catch (error) { + console.error("Error closing ticket:", error); + await interaction.reply({ + content: "❌ An error occurred while closing the ticket.", + ephemeral: true, + }); + } } diff --git a/events/modalCreate.js b/events/modalCreate.js index 4015bae..bacad6c 100644 --- a/events/modalCreate.js +++ b/events/modalCreate.js @@ -2,196 +2,196 @@ const { Events, EmbedBuilder } = require("discord.js"); const Database = require("../utils/database"); module.exports = { - name: Events.InteractionCreate, - async execute(interaction, client) { - // 📝 Handle modal submissions - if (interaction.isModalSubmit()) { - if (interaction.customId === "ai_context_modal") { - await handleAIContextModal(interaction, client); - } - } - - // 🎮 Handle button interactions for tickets - if (interaction.isButton()) { - if (interaction.customId.startsWith("ticket_")) { - await handleTicketButtons(interaction, client); - } - } - }, + name: Events.InteractionCreate, + async execute(interaction, client) { + // 📝 Handle modal submissions + if (interaction.isModalSubmit()) { + if (interaction.customId === "ai_context_modal") { + await handleAIContextModal(interaction, client); + } + } + + // 🎮 Handle button interactions for tickets + if (interaction.isButton()) { + if (interaction.customId.startsWith("ticket_")) { + await handleTicketButtons(interaction, client); + } + } + }, }; // 🤖 Handle AI context modal submission async function handleAIContextModal(interaction, client) { - const context = interaction.fields.getTextInputValue("ai_context_input"); - const db = new Database(); - - try { - await db.updateServerConfig(interaction.guild.id, { - ai_context: context, - }); - - const successEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("✅ AI Context Updated") - .setDescription( - "Successfully updated the AI assistant context for your server." - ) - .addFields({ - name: "📝 Context Preview", - value: context.substring(0, 500) + (context.length > 500 ? "..." : ""), - inline: false, - }) - .setFooter({ - text: `Updated by ${interaction.user.tag}`, - iconURL: interaction.user.displayAvatarURL(), - }) - .setTimestamp(); - - await interaction.reply({ embeds: [successEmbed], ephemeral: true }); - - console.log( - `🤖 AI context updated for ${interaction.guild.name} by ${interaction.user.tag}` - ); - } catch (error) { - console.error("❌ AI context update error:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Update Failed") - .setDescription("Failed to update AI context. Please try again.") - .setFooter({ text: "Contact support if this issue persists." }); - - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } + const context = interaction.fields.getTextInputValue("ai_context_input"); + const db = await Database.getInstance(); + + try { + await db.updateServerConfig(interaction.guild.id, { + ai_context: context, + }); + + const successEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("✅ AI Context Updated") + .setDescription( + "Successfully updated the AI assistant context for your server.", + ) + .addFields({ + name: "📝 Context Preview", + value: context.substring(0, 500) + (context.length > 500 ? "..." : ""), + inline: false, + }) + .setFooter({ + text: `Updated by ${interaction.user.tag}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await interaction.reply({ embeds: [successEmbed], ephemeral: true }); + + console.log( + `🤖 AI context updated for ${interaction.guild.name} by ${interaction.user.tag}`, + ); + } catch (error) { + console.error("❌ AI context update error:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Update Failed") + .setDescription("Failed to update AI context. Please try again.") + .setFooter({ text: "Contact support if this issue persists." }); + + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); + } } // 🎫 Handle ticket button interactions async function handleTicketButtons(interaction, client) { - const [action, type, ticketId] = interaction.customId.split("_"); - const db = new Database(); - - try { - switch (type) { - case "claim": - await handleTicketClaim(interaction, client, db, ticketId); - break; - case "close": - await handleTicketClose(interaction, client, db, ticketId); - break; - case "priority": - await handleTicketPriority(interaction, client, db, ticketId); - break; - } - } catch (error) { - console.error("❌ Ticket button error:", error); - - const errorEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("❌ Action Failed") - .setDescription("Failed to perform the requested action.") - .setFooter({ text: "Please try again or contact an administrator." }); - - if (interaction.replied || interaction.deferred) { - await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); - } else { - await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); - } - } + const [action, type, ticketId] = interaction.customId.split("_"); + const db = await Database.getInstance(); + + try { + switch (type) { + case "claim": + await handleTicketClaim(interaction, client, db, ticketId); + break; + case "close": + await handleTicketClose(interaction, client, db, ticketId); + break; + case "priority": + await handleTicketPriority(interaction, client, db, ticketId); + break; + } + } catch (error) { + console.error("❌ Ticket button error:", error); + + const errorEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("❌ Action Failed") + .setDescription("Failed to perform the requested action.") + .setFooter({ text: "Please try again or contact an administrator." }); + + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); + } else { + await interaction.reply({ embeds: [errorEmbed], ephemeral: true }); + } + } } // 🙋 Handle ticket claim async function handleTicketClaim(interaction, client, db, ticketId) { - const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); + const { isModeratorOrOwner } = require("../utils/moderation"); - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Permission Denied") - .setDescription("Only moderators can claim tickets.") - .setFooter({ text: "Contact a server administrator." }); + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Permission Denied") + .setDescription("Only moderators can claim tickets.") + .setFooter({ text: "Contact a server administrator." }); - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } - await db.updateTicketStatus(ticketId, "in_progress", interaction.user.id); + await db.updateTicketStatus(ticketId, "in_progress", interaction.user.id); - const claimEmbed = new EmbedBuilder() - .setColor(client.colors.success) - .setTitle("🙋 Ticket Claimed") - .setDescription( - `${interaction.user} has claimed this ticket and will assist you.` - ) - .setTimestamp(); + const claimEmbed = new EmbedBuilder() + .setColor(client.colors.success) + .setTitle("🙋 Ticket Claimed") + .setDescription( + `${interaction.user} has claimed this ticket and will assist you.`, + ) + .setTimestamp(); - await interaction.reply({ embeds: [claimEmbed] }); + await interaction.reply({ embeds: [claimEmbed] }); - console.log( - `🎫 Ticket #${ticketId} claimed by ${interaction.user.tag} in ${interaction.guild.name}` - ); + console.log( + `🎫 Ticket #${ticketId} claimed by ${interaction.user.tag} in ${interaction.guild.name}`, + ); } // 🔒 Handle ticket close async function handleTicketClose(interaction, client, db, ticketId) { - const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); - - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Permission Denied") - .setDescription("Only moderators can close tickets.") - .setFooter({ text: "Contact a server administrator." }); - - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } - - await db.updateTicketStatus(ticketId, "closed", interaction.user.id); - - const closeEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🔒 Ticket Closed") - .setDescription( - `This ticket has been closed by ${interaction.user}.\n\nThe channel will be deleted in 30 seconds.` - ) - .setFooter({ text: "Thank you for using our support system!" }) - .setTimestamp(); - - await interaction.reply({ embeds: [closeEmbed] }); - - // 🗑️ Delete channel after 30 seconds - setTimeout(async () => { - try { - await interaction.channel.delete("Ticket closed"); - } catch (error) { - console.error("❌ Error deleting ticket channel:", error); - } - }, 30000); - - console.log( - `🎫 Ticket #${ticketId} closed by ${interaction.user.tag} in ${interaction.guild.name}` - ); + const { isModeratorOrOwner } = require("../utils/moderation"); + + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Permission Denied") + .setDescription("Only moderators can close tickets.") + .setFooter({ text: "Contact a server administrator." }); + + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } + + await db.updateTicketStatus(ticketId, "closed", interaction.user.id); + + const closeEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🔒 Ticket Closed") + .setDescription( + `This ticket has been closed by ${interaction.user}.\n\nThe channel will be deleted in 30 seconds.`, + ) + .setFooter({ text: "Thank you for using our support system!" }) + .setTimestamp(); + + await interaction.reply({ embeds: [closeEmbed] }); + + // 🗑️ Delete channel after 30 seconds + setTimeout(async () => { + try { + await interaction.channel.delete("Ticket closed"); + } catch (error) { + console.error("❌ Error deleting ticket channel:", error); + } + }, 30000); + + console.log( + `🎫 Ticket #${ticketId} closed by ${interaction.user.tag} in ${interaction.guild.name}`, + ); } // 📊 Handle ticket priority change async function handleTicketPriority(interaction, client, db, ticketId) { - const { isModeratorOrOwner } = require("@adb/server/utils/moderation"); - - if (!isModeratorOrOwner(interaction.member, interaction.guild)) { - const noPermEmbed = new EmbedBuilder() - .setColor(client.colors.error) - .setTitle("🚫 Permission Denied") - .setDescription("Only moderators can change ticket priority.") - .setFooter({ text: "Contact a server administrator." }); - - return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); - } - - const priorityEmbed = new EmbedBuilder() - .setColor(client.colors.primary) - .setTitle("📊 Priority Change") - .setDescription( - "Priority change functionality will be implemented in the next update!" - ) - .setFooter({ text: "Coming soon!" }); - - await interaction.reply({ embeds: [priorityEmbed], ephemeral: true }); + const { isModeratorOrOwner } = require("../utils/moderation"); + + if (!isModeratorOrOwner(interaction.member, interaction.guild)) { + const noPermEmbed = new EmbedBuilder() + .setColor(client.colors.error) + .setTitle("🚫 Permission Denied") + .setDescription("Only moderators can change ticket priority.") + .setFooter({ text: "Contact a server administrator." }); + + return interaction.reply({ embeds: [noPermEmbed], ephemeral: true }); + } + + const priorityEmbed = new EmbedBuilder() + .setColor(client.colors.primary) + .setTitle("📊 Priority Change") + .setDescription( + "Priority change functionality will be implemented in the next update!", + ) + .setFooter({ text: "Coming soon!" }); + + await interaction.reply({ embeds: [priorityEmbed], ephemeral: true }); }