diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..0ca0bb8 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node.js CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: node bot.js diff --git a/README.md b/README.md index 7667303..bd4d449 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ - Open the terminal and run the following commands ``` -git clone https://github.com/friday2su/discord-js-bot.git +git clone https://github.com/Bd4xtahomid/All-In-One-updated.git cd discord-js-bot npm install ``` diff --git a/config.js b/config.js index 36d6825..c660689 100644 --- a/config.js +++ b/config.js @@ -1,6 +1,6 @@ module.exports = { - OWNER_IDS: ["1203605618745933880"], // Bot owner ID's - SUPPORT_SERVER: "https://discord.gg/btQJeGKKHJ", // Your bot support server + OWNER_IDS: ["1181137505505001544"], // Bot owner ID's + SUPPORT_SERVER: "https://discord.gg/MYVauhhhCT", // Your bot support server PREFIX_COMMANDS: { ENABLED: true, // Enable/Disable prefix commands DEFAULT_PREFIX: "e!", // Default prefix for the bot diff --git a/src/Oldmsg.js b/src/Oldmsg.js new file mode 100644 index 0000000..f922709 --- /dev/null +++ b/src/Oldmsg.js @@ -0,0 +1,36 @@ +const { commandHandler, automodHandler, statsHandler } = require("@src/handlers"); +const { PREFIX_COMMANDS } = require("@root/config"); +const { getSettings } = require("@schemas/Guild"); + +/** + * @param {import('@src/structures').BotClient} client + * @param {import('discord.js').Message} message + */ +module.exports = async (client, message) => { + if (!message.guild || message.author.bot) return; + const settings = await getSettings(message.guild); + + // command handler + let isCommand = false; + if (PREFIX_COMMANDS.ENABLED) { + // check for bot mentions + if (message.content.includes(`${client.user.id}`)) { + message.channel.safeSend(`> My prefix is \`${settings.prefix}\``); + } + + if (message.content && message.content.startsWith(settings.prefix)) { + const invoke = message.content.replace(`${settings.prefix}`, "").split(/\s+/)[0]; + const cmd = client.getCommand(invoke); + if (cmd) { + isCommand = true; + commandHandler.handlePrefixCommand(message, cmd, settings); + } + } + } + + // stats handler + if (settings.stats.enabled) await statsHandler.trackMessageStats(message, isCommand, settings); + + // if not a command + if (!isCommand) await automod Handler.performAutomod(message, settings); +}; diff --git a/src/commands/admin/hide.js b/src/commands/admin/hide.js new file mode 100644 index 0000000..56ef3bc --- /dev/null +++ b/src/commands/admin/hide.js @@ -0,0 +1,57 @@ +const { ApplicationCommandOptionType, EmbedBuilder } = require('discord.js'); + +module.exports = { + name: 'hide', + description: 'Hides a channel', + category: 'ADMIN', + userPermissions: ['ManageChannels'], + + command: { + enabled: true, + usage: '', + minArgsCount: 1, + }, + + slashCommand: { + enabled: false, + ephemeral: true, + options: [ + { + name: 'channel', + description: 'Channel to hide', + type: ApplicationCommandOptionType.Channel, + required: true, + }, + ], + }, + + async messageRun(message, args) { + const channel = message.mentions.channels.first(); + if (!channel) return message.channel.send('Please enter a channel to hide'); + + await channel.permissionOverwrites.edit(message.guild.roles.everyone, { + 'ViewChannel': false, + }); + + const embed = new EmbedBuilder() + .setTitle('✅ **Channel Hidden**') + .setDescription(`${channel} is now hidden from everyone.`) + .setColor('#e74c3c'); + + channel.send({ embeds: [embed] }); + }, + + async interactionRun(interaction) { + const channel = interaction.options.getChannel('channel'); + await channel.permissionOverwrites.edit(interaction.guild.roles.everyone, { + 'ViewChannel': false, + }); + + const embed = new EmbedBuilder() + .setTitle('✅ **Channel Hidden**') + .setDescription(`${channel} is now hidden from everyone.`) + .setColor('#e74c3c'); + + interaction.followUp({ embeds: [embed] }); + }, +}; \ No newline at end of file diff --git a/src/commands/admin/steal.js b/src/commands/admin/steal.js new file mode 100644 index 0000000..d48d0de --- /dev/null +++ b/src/commands/admin/steal.js @@ -0,0 +1,115 @@ +const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); + +module.exports = { + name: "steal", + description: "Steal emojis or stickers from other servers", + category: "ADMIN", + command: { + enabled: true, + usage: "COMMAND", + minArgsCount: 2, + subcommands: [ + { + trigger: "emoji ", + description: "Steal an emoji from another server", + }, + { + trigger: "sticker ", + description: "Steal a sticker from another server", + }, + ], + }, + slashCommand: { + enabled: true, + options: [ + { + name: "emoji", + description: "Steal an emoji from another server", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "emoji", + description: "The emoji to steal", + type: ApplicationCommandOptionType.String, + required: true, + }, + { + name: "name", + description: "Name for the new emoji", + type: ApplicationCommandOptionType.String, + required: true, + }, + ], + }, + { + name: "sticker", + description: "Steal a sticker from another server", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "url", + description: "URL of the sticker to steal", + type: ApplicationCommandOptionType.String, + required: true, + }, + { + name: "name", + description: "Name for the new sticker", + type: ApplicationCommandOptionType.String, + required: true, + }, + ], + }, + ], + }, + + async messageRun(message, args) { + const subcommand = args.shift(); + const response = await handleSteal(subcommand, args, message.guild); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const subcommand = interaction.options.getSubcommand(); + const response = await handleSteal(subcommand, interaction.options, interaction.guild); + await interaction.followUp(response); + }, +}; + +async function handleSteal(subcommand, options, guild) { + let embed; + + if (subcommand === "emoji") { + const emoji = options.getString ? options.getString("emoji") : options.shift(); + const name = options.getString ? options.getString("name") : options.join(" "); + const emojiId = emoji.match(/\d+/)[0]; + const emojiURL = `https://cdn.discordapp.com/emojis/${emojiId}.png`; + + try { + await guild.emojis.create({ attachment: emojiURL, name }); + embed = new EmbedBuilder() + .setColor("Green") + .setDescription(`Emoji **${name}** has been added successfully!`); + } catch { + embed = new EmbedBuilder() + .setColor("Red") + .setDescription("Unable to add emoji. Please check if the emoji source is valid."); + } + } else if (subcommand === "sticker") { + const url = options.getString ? options.getString("url") : options.shift(); + const name = options.getString ? options.getString("name") : options.join(" "); + + try { + await guild.stickers.create({ file: url, name }); + embed = new EmbedBuilder() + .setColor("Green") + .setDescription(`Sticker **${name}** has been added successfully!`); + } catch { + embed = new EmbedBuilder() + .setColor("Red") + .setDescription("Unable to add sticker. Please verify that the URL is correct."); + } + } + + return { embeds: [embed] }; +} \ No newline at end of file diff --git a/src/commands/admin/unhide.js b/src/commands/admin/unhide.js new file mode 100644 index 0000000..3c59ee9 --- /dev/null +++ b/src/commands/admin/unhide.js @@ -0,0 +1,57 @@ +const { ApplicationCommandOptionType, EmbedBuilder } = require('discord.js'); + +module.exports = { + name: 'unhide', + description: 'Unhides a channel', + category: 'ADMIN', + userPermissions: ['ManageChannels'], + + command: { + enabled: true, + usage: '', + minArgsCount: 1, + }, + + slashCommand: { + enabled: false, + ephemeral: true, + options: [ + { + name: 'channel', + description: 'Channel to unhide', + type: ApplicationCommandOptionType.Channel, + required: true, + }, + ], + }, + + async messageRun(message, args) { + const channel = message.mentions.channels.first(); + if (!channel) return message.channel.send('Please enter a channel to unhide'); + + await channel.permissionOverwrites.edit(message.guild.roles.everyone, { + 'ViewChannel': null, + }); + + const embed = new EmbedBuilder() + .setTitle('✅ **Channel Unhidden**') + .setDescription(`${channel} is now visible to everyone.`) + .setColor('#e74c3c'); + + channel.send({ embeds: [embed] }); + }, + + async interactionRun(interaction) { + const channel = interaction.options.getChannel('channel'); + await channel.permissionOverwrites.edit(interaction.guild.roles.everyone, { + 'ViewChannel': null, + }); + + const embed = new EmbedBuilder() + .setTitle('✅ **Channel Unhidden**') + .setDescription(`${channel} is now visible to everyone.`) + .setColor('#e74c3c'); + + interaction.followUp({ embeds: [embed] }); + }, +}; \ No newline at end of file diff --git a/src/commands/fun/fun.js b/src/commands/fun/fun.js new file mode 100644 index 0000000..db4d570 --- /dev/null +++ b/src/commands/fun/fun.js @@ -0,0 +1,960 @@ +const { + EmbedBuilder, + ApplicationCommandOptionType , + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, +} = require("discord.js"); +const axios = require("axios"); +const { TicTacToe, RockPaperScissors, FastType, Snake, WouldYouRather, Hangman, FindEmoji, GuessThePokemon, Wordle, Flood, Connect4, TwoZeroFourEight, Minesweeper, MatchPairs } = require("discord-gamecord"); + +/** + * @type {import("@structures/Command")} + */ + +module.exports = { + name: "fun", + description: "play some cool fun games", + cooldown: 0, + category: "FUN", + botPermissions: ["EmbedLinks"], + userPermissions: [], + command: { + enabled: true, + aliases: [], + usage: "[COMMAND]", + minArgsCount: 0, + }, + slashCommand: { + // ephemeral: true, + enabled: true, + options: [ + { + name: "tictactoe", + description: "Plays TicTacToe Game", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "opponent", + description: "The opponent with whom you wanna play", + type: ApplicationCommandOptionType.User, + required: true, + } + ] + }, + { + name: "rps", + description: "Starts a Rock Paper Scissor Game", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "opponent", + description: "The opponent you want to play", + type: ApplicationCommandOptionType.User, + required: true, + } + ] + }, + { + name: "fasttype", + description: "Starts a Fast Type Game", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "snake", + description: "Starts a game of Snake.", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "wouldyourather", + description: "Play Would You Rather Game", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "hangman", + description: "Play a game of Hangman", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "findemoji", + description: "Play a game of Find Emoji", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "guessthepokemon", + description: "Play Guess the Pokemon Game", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "wordle", + description: "Play a game of Wordle", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "flood", + description: "Play a game of Flood", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "connect4", + description: "Play a game of Connect4", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "opponent", + description: "The user you want to play this game against.", + type: ApplicationCommandOptionType.User, + required: true, + } + ] + }, + { + name: "2048", + description: "Play a game of 2048", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "minesweeper", + description: "Play a game of minesweeper", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "matchpairs", + description: "Play a game of matchpairs", + type: ApplicationCommandOptionType.Subcommand, + }, + { + name: "truthdare", + description: "Play a game of Truth or Dare", + type: ApplicationCommandOptionType.Subcommand, + } + ], + }, + + async messageRun(message, args, data) { + const subcommand = args[0]; + + // Handle each subcommand + if (subcommand === "tictactoe") { + const opponentUser = message.mentions.users.first(); + if (!opponentUser) { + await message.safeReply("Please mention the opponent you want to play against."); + return; + } + if (opponentUser.id === message.author.id) { + await message.safeReply("You can't add yourself as an opponent."); + return; + } + if (opponentUser.bot) { + await message.safeReply("You can't add bots to opponent") + return; + } + const Game = new TicTacToe({ + message: message, + isSlashGame: false, + opponent: opponentUser, + embed: { + title: 'Tic Tac Toe', + color: '#5865F2', + statusTitle: 'Status', + overTitle: 'Game Over' + }, + emojis: { + xButton: '❌', + oButton: '🔵', + blankButton: '➖' + }, + mentionUser: true, + timeoutTime: 60000, + xButtonStyle: 'DANGER', + oButtonStyle: 'PRIMARY', + turnMessage: '{emoji} | Its turn of player **{player}**.', + winMessage: '{emoji} | **{player}** won the TicTacToe Game.', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "rps") { + const opponentUser = message.mentions.users.first(); + if (!opponentUser) { + await message.safeReply("Please mention the opponent you want to play against."); + return; + } + if (opponentUser.id === message.author.id) { + await message.safeReply("You can't add yourself as an opponent."); + return; + } + if (opponentUser.bot) { + await message.safeReply("You can't add bots to opponent") + return; + } + const Game = new RockPaperScissors({ + message: message, + isSlashGame: false, + opponent: opponentUser, + embed: { + title: 'Rock Paper Scissors', + color: '#5865F2', + description: 'Press a button below to make a choice.' + }, + buttons: { + rock: 'Rock', + paper: 'Paper', + scissors: 'Scissors' + }, + emojis: { + rock: '🌑', + paper: '📰', + scissors: '✂️' + }, + mentionUser: true, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + pickMessage: 'You choose {emoji}.', + winMessage: '**{player}** won the Game! Congratulations!', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "fasttype") { + const Game = new FastType({ + message: message, + isSlashGame: false, + embed: { + title: 'Fast Type', + color: '#5865F2', + description: 'You have {time} seconds to type the sentence below.', + }, + timeoutTime: 60000, + sentence: "UltimateBot on Top.", + winMessage: + "You won! You finished the type race in {time} seconds with wpm of {wpm}.", + loseMessage: "You lost! You didn't type the correct sentence in time.", + }); + + Game.startGame(); + } else if (subcommand === "snake") { + const Game = new Snake({ + message: message, + isSlashGame: false, + embed: { + title: 'Snake Game', + overTitle: 'Game Over', + color: '#5865F2' + }, + emojis: { + board: '⬛', + food: '🍎', + up: '⬆️', + down: '⬇️', + left: '⬅️', + right: '➡️', + }, + snake: { head: '🟢', body: '🟩', tail: '🟢', over: '💀' }, + foods: ['🍎', '🍇', '🍊', '🫐', '🥕', '🥝', '🌽'], + stopButton: 'Stop', + timeoutTime: 60000, + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "wouldyourather") { + const game = new WouldYouRather({ + message: message, + isSlashGame: false, + embed: { + title: "Would You Rather", + color: "#5865F2", + }, + buttons: { + option1: "Option 1", + option2: "Option 2", + }, + timeoutTime: 60000, + errMessage: "Unable to fetch question data! Please try again.", + playerOnlyMessage: "Only {player} can use these buttons.", + }); + + game.startGame(); + } else if (subcommand === "hangman") { + // Hangman game logic + const Game = new Hangman({ + message: message, + isSlashGame: false, + embed: { + title: 'Hangman', + color: '#5865F2' + }, + hangman: { hat: '🎩', head: '😟', shirt: '👕', pants: '🩳', boots: '👞👞' }, + customWord: undefined, + timeoutTime: 60000, + theme: 'nature', + winMessage: 'You won! The word was **{word}**.', + loseMessage: 'You lost! The word was **{word}**.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "findemoji") { + // Find Emoji game logic + const Game = new FindEmoji({ + message: message, + isSlashGame: false, + embed: { + title: 'Find Emoji', + color: '#5865F2', + description: 'Remember the emojis from the board below.', + findDescription: 'Find the {emoji} emoji before the time runs out.' + }, + timeoutTime: 60000, + hideEmojiTime: 5000, + buttonStyle: 'PRIMARY', + emojis: ['🍉', '🍇', '🍊', '🍋', '🥭', '🍎', '🍏', '🥝'], + winMessage: 'You won! You selected the correct emoji. {emoji}', + loseMessage: 'You lost! You selected the wrong emoji. {emoji}', + timeoutMessage: 'You lost! You ran out of time. The emoji is {emoji}', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "guessthepokemon") { + const Game = new GuessThePokemon({ + message: message, + isSlashGame: false, + embed: { + title: 'Who\'s The Pokemon', + color: '#5865F2' + }, + timeoutTime: 60000, + winMessage: 'You guessed it right! It was a {pokemon}.', + loseMessage: 'Better luck next time! It was a {pokemon}.', + errMessage: 'Unable to fetch pokemon data! Please try again.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "wordle") { + const Game = new Wordle({ + message: message, + isSlashGame: false, + embed: { + title: 'Wordle', + color: '#5865F2', + }, + customWord: null, + timeoutTime: 60000, + winMessage: 'You won! The word was **{word}**.', + loseMessage: 'You lost! The word was **{word}**.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "flood") { + const Game = new Flood({ + message: message, + isSlashGame: false, + embed: { + title: 'Flood', + color: '#5865F2', + }, + difficulty: 13, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + emojis: ['🟥', '🟦', '🟧', '🟪', '🟩'], + winMessage: 'You won! You took **{turns}** turns.', + loseMessage: 'You lost! You took **{turns}** turns.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "connect4") { + const opponentUser = message.mentions.members.first() || message.guild.members.cache.get(args[1]); + if (!opponentUser) { + await message.safeReply("Please mention the opponent you want to play against."); + return; + } + if (opponentUser.id === message.author.id) { + await message.safeReply("You can't add yourself as an opponent."); + return; + } + if (opponentUser.bot) { + await message.safeReply("You can't add bots to opponent") + return; + } + const Game = new Connect4({ + message: message, + isSlashGame: false, + opponent: message.mentions.users.first(), + embed: { + title: 'Connect4 Game', + statusTitle: 'Status', + color: '#5865F2' + }, + emojis: { + board: '⚪', + player1: '🔴', + player2: '🟡' + }, + mentionUser: true, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + turnMessage: '{emoji} | Its turn of player **{player}**.', + winMessage: '{emoji} | **{player}** won the Connect4 Game.', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "2048") { + const Game = new TwoZeroFourEight({ + message: message, + isSlashGame: false, + embed: { + title: '2048', + color: '#5865F2' + }, + emojis: { + up: '⬆️', + down: '⬇️', + left: '⬅️', + right: '➡️', + }, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "minesweeper") { + const Game = new Minesweeper({ + message: message, + isSlashGame: false, + embed: { + title: 'Minesweeper', + color: '#5865F2', + description: 'Click on the buttons to reveal the blocks except mines.' + }, + emojis: { flag: '🚩', mine: '💣' }, + mines: 5, + timeoutTime: 60000, + winMessage: 'You won the game! You successfully avoided all the mines.', + loseMessage: 'You lost the game! Be aware of the mines next time.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "matchpairs") { + const Game = new MatchPairs({ + message: message, + isSlashGame: false, + embed: { + title: 'Match Pairs', + color: '#5865F2', + description: '**Click on the buttons to match emojis with their pairs.**' + }, + timeoutTime: 60000, + emojis: ['🍉', '🍇', '🍊', '🥭', '🍎', '🍏', '🥝', '🥥', '🍓', '🍌', '🍍', '🥕', '🥔'], + winMessage: '**You won the Game! You turned a total of `{tilesTurned}` tiles.**', + loseMessage: '**You lost the Game! You turned a total of `{tilesTurned}` tiles.**', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (subcommand === "truthdare") { + const truthDareEmbed = new EmbedBuilder() + .setTitle("Truth or Dare") + .setDescription("Click a button to choose either Truth or Dare!") + .setColor("#e74c3c"); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('truth') + .setLabel('Truth') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('dare') + .setLabel('Dare') + .setStyle(ButtonStyle.Danger) + ); + + const sentMessage = await message.safeReply({ embeds: [truthDareEmbed], components: [row] }); + + const filter = i => ['truth', 'dare'].includes(i.customId) && i.user.id === message.author.id; + const collector = sentMessage.createMessageComponentCollector({ filter, componentType: ComponentType.Button, time: 60000 }); + + collector.on('collect', async i => { + let content; + let title; + if (i.customId === 'truth') { + const truth = await getTruthOrDare('truth'); + content = `${truth}`; + title = "Truth Result"; + } else if (i.customId === 'dare') { + const dare = await getTruthOrDare('dare'); + content = `${dare}`; + title = "Dare Result"; + } + + const resultEmbed = new EmbedBuilder() + .setTitle(title) + .setDescription(content) + .setColor("#e74c3c"); + + await i.reply({ embeds: [resultEmbed], ephemeral: false }); + + const updatedRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('truth') + .setLabel('Truth') + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setCustomId('dare') + .setLabel('Dare') + .setStyle(ButtonStyle.Danger) + .setDisabled(true) + ); + + await sentMessage.edit({ components: [updatedRow] }); + }); + + collector.on('end', collected => { + if (!collected.size) { + const noChoiceEmbed = new EmbedBuilder() + .setDescription('No choice was made in time!') + .setColor("#e74c3c"); + sentMessage.edit({ embeds: [noChoiceEmbed], components: [] }); + } + }); + } else { + // Handle unknown subcommands + message.channel.send("Unknown subcommand."); + } + }, + + async interactionRun(interaction, data) { + const subgroup = interaction.options.getSubcommandGroup() + const sub = interaction.options.getSubcommand() + if (sub === "tictactoe") { + const opponentUser = interaction.options.getMember("opponent") + if (opponentUser.id === interaction.member.id) { + await interaction.followUp("You can't add yourself to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + const Game = new TicTacToe({ + message: interaction, + isSlashGame: true, + opponent: opponentUser.user, + embed: { + title: 'Tic Tac Toe', + color: '#5865F2', + statusTitle: 'Status', + overTitle: 'Game Over' + }, + emojis: { + xButton: '❌', + oButton: '🔵', + blankButton: '➖' + }, + mentionUser: true, + timeoutTime: 60000, + xButtonStyle: 'DANGER', + oButtonStyle: 'PRIMARY', + turnMessage: '{emoji} | Its turn of player **{player}**.', + winMessage: '{emoji} | **{player}** won the TicTacToe Game.', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "rps") { + const opponentUser = interaction.options.getMember("opponent") + if (opponentUser.id === interaction.member.id) { + await interaction.followUp("You can't add yourself to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + const Game = new RockPaperScissors({ + message: interaction, + isSlashGame: true, + opponent: opponentUser.user, + embed: { + title: 'Rock Paper Scissors', + color: '#5865F2', + description: 'Press a button below to make a choice.' + }, + buttons: { + rock: 'Rock', + paper: 'Paper', + scissors: 'Scissors' + }, + emojis: { + rock: '🌑', + paper: '📰', + scissors: '✂️' + }, + mentionUser: true, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + pickMessage: 'You choose {emoji}.', + winMessage: '**{player}** won the Game! Congratulations!', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "fasttype") { + const Game = new FastType({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Fast Type', + color: '#5865F2', + description: 'You have {time} seconds to type the sentence below.', + }, + timeoutTime: 60000, + sentence: "UltimateBot on top.", + winMessage: + "You won! You finished the type race in {time} seconds with wpm of {wpm}.", + loseMessage: "You lost! You didn't type the correct sentence in time.", + }); + + Game.startGame(); + } else if (sub === "snake") { + const Game = new Snake({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Snake Game', + overTitle: 'Game Over', + color: '#5865F2' + }, + emojis: { + board: '⬛', + food: '🍎', + up: '⬆️', + down: '⬇️', + left: '⬅️', + right: '➡️', + }, + snake: { head: '🟢', body: '🟩', tail: '🟢', over: '💀' }, + foods: ['🍎', '🍇', '🍊', '🫐', '🥕', '🥝', '🌽'], + stopButton: 'Stop', + timeoutTime: 60000, + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "wouldyourather") { + const game = new WouldYouRather({ + message: interaction, + isSlashGame: true, + embed: { + title: "Would You Rather", + color: "#5865F2", + }, + buttons: { + option1: "Option 1", + option2: "Option 2", + }, + timeoutTime: 60000, + errMessage: "Unable to fetch question data! Please try again.", + playerOnlyMessage: "Only {player} can use these buttons.", + }); + + game.startGame(); + } else if (sub === "hangman") { + // Hangman game logic for interactions + const Game = new Hangman({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Hangman', + color: '#5865F2' + }, + hangman: { hat: '🎩', head: '😟', shirt: '👕', pants: '🩳', boots: '👞👞' }, + customWord: undefined, + timeoutTime: 60000, + theme: 'nature', + winMessage: 'You won! The word was **{word}**.', + loseMessage: 'You lost! The word was **{word}**.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "findemoji") { + // Find Emoji game logic for interactions + const Game = new FindEmoji({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Find Emoji', + color: '#5865F2', + description: 'Remember the emojis from the board below.', + findDescription: 'Find the {emoji} emoji before the time runs out.' + }, + timeoutTime: 60000, + hideEmojiTime: 5000, + buttonStyle: 'PRIMARY', + emojis: ['🍉', '🍇', '🍊', '🍋', '🥭', '🍎', '🍏', '🥝'], + winMessage: 'You won! You selected the correct emoji. {emoji}', + loseMessage: 'You lost! You selected the wrong emoji. {emoji}', + timeoutMessage: 'You lost! You ran out of time. The emoji is {emoji}', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "guessthepokemon") { + const Game = new GuessThePokemon({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Who\'s The Pokemon', + color: '#5865F2' + }, + timeoutTime: 60000, + winMessage: 'You guessed it right! It was a {pokemon}.', + loseMessage: 'Better luck next time! It was a {pokemon}.', + errMessage: 'Unable to fetch pokemon data! Please try again.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "wordle") { + const Game = new Wordle({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Wordle', + color: '#5865F2', + }, + customWord: null, + timeoutTime: 60000, + winMessage: 'You won! The word was **{word}**.', + loseMessage: 'You lost! The word was **{word}**.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "flood") { + const Game = new Flood({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Flood', + color: '#5865F2', + }, + difficulty: 13, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + emojis: ['🟥', '🟦', '🟧', '🟪', '🟩'], + winMessage: 'You won! You took **{turns}** turns.', + loseMessage: 'You lost! You took **{turns}** turns.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "connect4") { + const opponentUser = interaction.options.getMember("opponent") + if (opponentUser.id === interaction.member.id) { + await interaction.followUp("You can't add yourself to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + if (opponentUser.user.bot) { + await interaction.followUp("You can't add bots to opponent") + return; + } + const Game = new Connect4({ + message: interaction, + isSlashGame: true, + opponent: interaction.options.getUser('opponent'), + embed: { + title: 'Connect4 Game', + statusTitle: 'Status', + color: '#5865F2' + }, + emojis: { + board: '⚪', + player1: '🔴', + player2: '🟡' + }, + mentionUser: true, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + turnMessage: '{emoji} | Its turn of player **{player}**.', + winMessage: '{emoji} | **{player}** won the Connect4 Game.', + tieMessage: 'The Game tied! No one won the Game!', + timeoutMessage: 'The Game went unfinished! No one won the Game!', + playerOnlyMessage: 'Only {player} and {opponent} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "2048") { + const Game = new TwoZeroFourEight({ + message: interaction, + isSlashGame: true, + embed: { + title: '2048', + color: '#5865F2' + }, + emojis: { + up: '⬆️', + down: '⬇️', + left: '⬅️', + right: '➡️', + }, + timeoutTime: 60000, + buttonStyle: 'PRIMARY', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "minesweeper") { + const Game = new Minesweeper({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Minesweeper', + color: '#5865F2', + description: 'Click on the buttons to reveal the blocks except mines.' + }, + emojis: { flag: '🚩', mine: '💣' }, + mines: 5, + timeoutTime: 60000, + winMessage: 'You won the game! You successfully avoided all the mines.', + loseMessage: 'You lost the game! Be aware of the mines next time.', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "matchpairs") { + const Game = new MatchPairs({ + message: interaction, + isSlashGame: true, + embed: { + title: 'Match Pairs', + color: '#5865F2', + description: '**Click on the buttons to match emojis with their pairs.**' + }, + timeoutTime: 60000, + emojis: ['🍉', '🍇', '🍊', '🥭', '🍎', '🍏', '🥝', '🥥', '🍓', '🍌', '🍍', '🥕', '🥔'], + winMessage: '**You won the Game! You turned a total of `{tilesTurned}` tiles.**', + loseMessage: '**You lost the Game! You turned a total of `{tilesTurned}` tiles.**', + playerOnlyMessage: 'Only {player} can use these buttons.' + }); + + Game.startGame(); + } else if (sub === "truthdare") { + const truthDareEmbed = new EmbedBuilder() + .setTitle("Truth or Dare") + .setDescription("Click a button to choose either Truth or Dare!") + .setColor("#e74c3c"); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('truth') + .setLabel('Truth') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId('dare') + .setLabel('Dare') + .setStyle(ButtonStyle.Danger) + ); + + const sentMessage = await interaction.followUp({ embeds: [truthDareEmbed], components: [row], ephemeral: true }); + + const filter = i => ['truth', 'dare'].includes(i.customId) && i.user.id === interaction.user.id; + const collector = interaction.channel.createMessageComponentCollector({ filter, componentType: ComponentType.Button, time: 60000 }); + + collector.on('collect', async i => { + let content; + let title; + if (i.customId === 'truth') { + const truth = await getTruthOrDare('truth'); + content = `${truth}`; + title = "Truth Result"; + } else if (i.customId === 'dare') { + const dare = await getTruthOrDare('dare'); + content = `${dare}`; + title = "Dare Result"; + } + + const resultEmbed = new EmbedBuilder() + .setTitle(title) + .setDescription(content) + .setColor("#e74c3c"); + + await i.reply({ embeds: [resultEmbed], ephemeral: false }); + + const updatedRow = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('truth') + .setLabel('Truth') + .setStyle(ButtonStyle.Primary) + .setDisabled(true), + new ButtonBuilder() + .setCustomId('dare') + .setLabel('Dare') + .setStyle(ButtonStyle.Danger) + .setDisabled(true) + ); + + await sentMessage.edit({ components: [updatedRow] }); + }); + + collector.on('end', collected => { + if (!collected.size) { + const noChoiceEmbed = new EmbedBuilder() + .setTitle("Timeout") + .setDescription('No choice was made in time!') + .setColor("#e74c3c"); + interaction.editReply({ embeds: [noChoiceEmbed], components: [] }); + } + }); + } else { + // Handle unknown subcommands + await interaction.followUp("Unknown subcommand."); + } + }, +}; + +async function getTruthOrDare(type) { + try { + const response = await axios.get(`https://api.truthordarebot.xyz/api/${type}`); + return response.data.question; + } catch (error) { + console.error(error); + return 'Sorry, something went wrong. Please try again!'; + } +} \ No newline at end of file diff --git a/src/commands/information/BoostCount.js b/src/commands/information/BoostCount.js new file mode 100644 index 0000000..4a0264c --- /dev/null +++ b/src/commands/information/BoostCount.js @@ -0,0 +1,39 @@ +const { EmbedBuilder } = require("discord.js"); +const { EMBED_COLORS } = require("@root/config.js"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "boostcount", + description: "to know guild boost", + category: "INFORMATION", + command: { + enabled: true, + aliases: ["bc", "bcount"], + }, + slashCommand: { + enabled: true, + options: [], + }, + + async messageRun(message, args, client) { + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setFooter({ text: "Requested by " + message.author.tag, iconURL: message.author.displayAvatarURL() }) + .setAuthor({ name: "BoostCount Panel", iconURL: message.client.user.displayAvatarURL() }) + .addFields([{ name: "**BoostCount**", value: `**${message.guild.premiumSubscriptionCount || '0'}**` }]); + + return message.safeReply({ embeds: [embed] }); + }, + + async interactionRun(interaction) { + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setFooter({ text: "Requested by " + interaction.user.tag, iconURL: interaction.user.displayAvatarURL() }) + .setAuthor({ name: "BoostCount Panel", iconURL: interaction.client.user.displayAvatarURL() }) + .addFields([{ name: "**BoostCount**", value: `**${interaction.guild.premiumSubscriptionCount || '0'}**` }]); + + return interaction.followUp({ embeds: [embed] }); + }, +}; diff --git a/src/commands/information/membercount.js b/src/commands/information/membercount.js new file mode 100644 index 0000000..434f4b0 --- /dev/null +++ b/src/commands/information/membercount.js @@ -0,0 +1,69 @@ +const { EmbedBuilder } = require("discord.js"); +const QuickChart = require('quickchart-js'); + +/** + * @type {import("@structures/Command")} + */ + +module.exports = { + name: "membercount", + description: "Shows the number of members on the server.", + category: "INFORMATION", + botPermissions: ["EmbedLinks"], + command: { + enabled: true, + aliases: [], + }, + slashCommand: { + enabled: true + }, + async messageRun(message) { + await message.reply(await getChartEmbed(message.guild)); + }, + async interactionRun(interaction) { + await interaction.followUp(await getChartEmbed(interaction.guild)); + } +} + +/** + * @param {import("discord.js").Guild} guild + */ +async function getChartEmbed(guild) { + const totalMembers = guild.memberCount; + const botMembers = guild.members.cache.filter(member => member.user.bot).size; + const humanMembers = totalMembers - botMembers; + const last24Hours = guild.members.cache.filter(member => Date.now() - member.joinedTimestamp < 24 * 60 * 60 * 1000).size; + const last7Days = guild.members.cache.filter(member => Date.now() - member.joinedTimestamp < 7 * 24 * 60 * 60 * 1000).size; + + const chart = new QuickChart(); + chart.setConfig({ + type: 'bar', + data: { + labels: ['Total', 'Members', 'Bots', '24h', '7 days'], + datasets: [{ + label: 'Member Count', + data: [totalMembers, humanMembers, botMembers, last24Hours, last7Days], + backgroundColor: ['#36a2eb', '#ffce56', '#ff6384', '#cc65fe', '#66ff99'] + }] + }, + options: { + plugins: { + title: { + display: true, + text: `${guild.name} members count` + } + } + }, + }).setWidth(500).setHeight(300).setBackgroundColor('#151515'); + + const chartUrl = await chart.getShortUrl(); + const embed = new EmbedBuilder().setTitle(`📕 │ INFORMATION`) + .setColor('#e74c3c') + .setFooter({ text: guild.client.user.username, iconURL: guild.client.user.avatarURL()}) + .setDescription(`Total: **${totalMembers}**\nMembers: **${humanMembers}**\nBots: **${botMembers}**\nLast 24h: **${last24Hours}**\nLast 7 days: **${last7Days}**`) + .setImage(chartUrl); + + return { + embeds: [embed] + } +} \ No newline at end of file diff --git a/src/commands/owner/noprefix.js b/src/commands/owner/noprefix.js new file mode 100644 index 0000000..0b07928 --- /dev/null +++ b/src/commands/owner/noprefix.js @@ -0,0 +1,99 @@ +const { EmbedBuilder, ApplicationCommandOptionType } = require("discord.js"); +const { EMBED_COLORS } = require("@root/config"); +const UserNoPrefix = require("../../database/schemas/NoPrefix"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "noprefix", + description: "Manage no-prefix settings for a user", + cooldown: 0, + category: "OWNER", + botPermissions: ["EmbedLinks"], + userPermissions: [], + command: { + enabled: true, + aliases: [], + usage: " ", + minArgsCount: 2, + }, + slashCommand: { + enabled: true, + options: [ + { + name: "user", + description: "The user id to manage no prefix settings for", + type: ApplicationCommandOptionType.String, + required: true, + }, + { + name: "action", + description: "Enable or disable no-prefix commands", + type: ApplicationCommandOptionType.String, + required: true, + choices: [ + { name: "Enable", value: "enable" }, + { name: "Disable", value: "disable" }, + ], + }, + ], + }, + + async messageRun(message, args) { + const userId = args.shift(); + const action = args.shift()?.toLowerCase(); + const response = await manageNoPrefix(userId, action, message.client); + + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const userId = interaction.options.getString("user"); + const action = interaction.options.getString("action"); + const response = await manageNoPrefix(userId, action, interaction.client); + + await interaction.followUp(response); + }, +}; + +async function manageNoPrefix(userId, action, client) { + const user = await client.users.fetch(userId); + + if (!user) { + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription("User not found."); + return { embeds: [embed] }; + } + + const userNoPrefix = await UserNoPrefix.findOne({ userId }); + + if (action === "enable" && userNoPrefix?.noPrefixEnabled) { + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(`${user.tag} already has no prefix access.`); + return { embeds: [embed] }; + } + + if (action === "disable" && !userNoPrefix?.noPrefixEnabled) { + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(`${user.tag} does not have no prefix access.`); + return { embeds: [embed] }; + } + + await UserNoPrefix.findOneAndUpdate( + { userId }, + { noPrefixEnabled: action === "enable" }, + { new: true, upsert: true } + ); + + const embed = new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setTitle(`No Prefix ${action === "enable" ? "Enabled" : "Disabled"}`) + .setDescription(`No prefix has been ${action === "enable" ? "enabled" : "disabled"} for ${user.tag}.`) + .setTimestamp(); + + return { embeds: [embed] }; +} \ No newline at end of file diff --git a/src/commands/utility/afk.js b/src/commands/utility/afk.js new file mode 100644 index 0000000..b450c90 --- /dev/null +++ b/src/commands/utility/afk.js @@ -0,0 +1,57 @@ +const { ApplicationCommandOptionType } = require("discord.js"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "afk", + description: "Set your AFK status", + category: "UTILITY", + command: { + enabled: true, + usage: "[reason]", + }, + slashCommand: { + enabled: true, + options: [ + { + name: "reason", + description: "reason for going AFK", + type: ApplicationCommandOptionType.String, + required: false, + }, + ], + }, + + async messageRun(message, args) { + const reason = args.join(" ") || "No reason provided"; + const response = await setupAFK(message.member, reason); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const reason = interaction.options.getString("reason") || "No reason provided"; + const response = await setupAFK(interaction.member, reason); + await interaction.followUp(response); + }, +}; + +async function setupAFK(member, reason) { + // Store AFK data in memory + member.client.afkUsers = member.client.afkUsers || new Map(); + member.client.afkUsers.set(member.id, { + timestamp: Date.now(), + reason: reason, + username: member.displayName + }); + + // Set nickname to show AFK status + try { + const oldNick = member.displayName; + await member.setNickname(`[AFK] ${oldNick}`); + } catch (err) { + // Ignore nickname setting errors + } + + return `I've set your AFK status: ${reason}`; +} \ No newline at end of file diff --git a/src/commands/utility/help.js b/src/commands/utility/help.js index 11e4456..8222ae0 100644 --- a/src/commands/utility/help.js +++ b/src/commands/utility/help.js @@ -110,19 +110,19 @@ async function getHelpMenu({ client, guild }) { .setLabel("Support Server") .setEmoji('1238430085795545139') .setStyle(ButtonStyle.Link) - .setURL("https://discord.gg/sNGBwSeZYM"); + .setURL("https://discord.gg/toz"); const inviteButton = new ButtonBuilder() .setLabel("Invite Me") .setStyle(ButtonStyle.Link) .setEmoji('1238429920573784074') - .setURL("https://discord.com/api/oauth2/authorize?client_id=1238431297974566972&permissions=8&scope=bot+applications.commands"); + .setURL("https://discord.com/api/oauth2/authorize?client_id=&permissions=13018590071154319568&scope=bot+applications.commands"); const voteButton = new ButtonBuilder() .setLabel("Vote Me") .setStyle(ButtonStyle.Link) .setEmoji('1238429509795971116') - .setURL("https://top.gg/servers/1143415296862978058/vote"); + .setURL("https://top.gg/1301859007115431956/vote"); const buttonsRow = new ActionRowBuilder().addComponents([supportButton, inviteButton, voteButton]); @@ -381,4 +381,4 @@ function getMsgCategoryEmbeds(client, category, prefix) { }); return arrEmbeds; -} \ No newline at end of file +} diff --git a/src/database/schemas/NoPrefix.js b/src/database/schemas/NoPrefix.js new file mode 100644 index 0000000..cecaa2e --- /dev/null +++ b/src/database/schemas/NoPrefix.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); + +const UserNoPrefixSchema = new mongoose.Schema({ + userId: { + type: String, + required: true, + unique: true, + }, + noPrefixEnabled: { + type: Boolean, + default: false, + }, +}, { + timestamps: true, +}); + +module.exports = mongoose.model('UserNoPrefix', UserNoPrefixSchema); \ No newline at end of file diff --git a/src/events/message/messageCreate.js b/src/events/message/messageCreate.js index 972688f..b35b5d5 100644 --- a/src/events/message/messageCreate.js +++ b/src/events/message/messageCreate.js @@ -1,6 +1,8 @@ const { commandHandler, automodHandler, statsHandler } = require("@src/handlers"); -const { PREFIX_COMMANDS } = require("@root/config"); +const { PREFIX_COMMANDS, EMBED_COLORS } = require("@root/config"); const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder } = require("discord.js"); +const UserNoPrefix = require("@schemas/NoPrefix"); /** * @param {import('@src/structures').BotClient} client @@ -10,16 +12,72 @@ module.exports = async (client, message) => { if (!message.guild || message.author.bot) return; const settings = await getSettings(message.guild); - // command handler - let isCommand = false; - if (PREFIX_COMMANDS.ENABLED) { - // check for bot mentions - if (message.content.includes(`${client.user.id}`)) { - message.channel.safeSend(`> My prefix is \`${settings.prefix}\``); + // AFK System Start + if (client.afkUsers && client.afkUsers.has(message.author.id)) { + client.afkUsers.delete(message.author.id); + + // Remove AFK from nickname + try { + const newNick = message.member.displayName.replace(/^\[AFK\] /, ''); + await message.member.setNickname(newNick); + } catch (err) { + // Ignore nickname errors } - if (message.content && message.content.startsWith(settings.prefix)) { - const invoke = message.content.replace(`${settings.prefix}`, "").split(/\s+/)[0]; + await message.reply({ + content: `Welcome back! I've removed your AFK status.`, + ephemeral: false + }); + } + + // Check for mentions of AFK users + if (message.mentions.users.size > 0 && client.afkUsers) { + message.mentions.users.forEach(async (mentionedUser) => { + if (client.afkUsers.has(mentionedUser.id)) { + const afkData = client.afkUsers.get(mentionedUser.id); + const timePassed = Math.floor((Date.now() - afkData.timestamp) / 1000 / 60); // in minutes + + await message.reply({ + content: `${mentionedUser.username} is AFK: ${afkData.reason} (${timePassed} minutes ago)`, + ephemeral: false + }); + } + }); + } + // AFK System End + + // Enhanced Bot Mention Response + if (message.content.startsWith(client.user.toString())) { + const messageContent = message.content.replace(/<@(!)?\d+>/, "").trim(); + if (!messageContent) { + const embed = new EmbedBuilder() + .setTitle("Hey, What's up?") + .setThumbnail(client.user.displayAvatarURL({ size: 2048, dynamic: true })) + .setDescription( + `Hello, ${message.author}! It seems like you summoned me. My prefix is \`${settings.prefix}\`. Invoke \`${settings.prefix}help\` to see my all commands!` + ) + .setColor(EMBED_COLORS.BOT_EMBED) + .setTimestamp(); + + message.safeReply({ embeds: [embed] }); + return; + } + } + + // Command Handler with No-Prefix Support + let isCommand = false; + + if (PREFIX_COMMANDS.ENABLED && message.content.startsWith(settings.prefix)) { + const invoke = message.content.replace(`${settings.prefix}`, "").split(/\s+/)[0]; + const cmd = client.getCommand(invoke); + if (cmd) { + isCommand = true; + commandHandler.handlePrefixCommand(message, cmd, settings); + } + } else { + const userNoPrefix = await UserNoPrefix.findOne({ userId: message.author.id }); + if (userNoPrefix && userNoPrefix.noPrefixEnabled) { + const invoke = message.content.split(/\s+/)[0].toLowerCase(); const cmd = client.getCommand(invoke); if (cmd) { isCommand = true; @@ -28,9 +86,7 @@ module.exports = async (client, message) => { } } - // stats handler + // Stats and Automod Handler if (settings.stats.enabled) await statsHandler.trackMessageStats(message, isCommand, settings); - - // if not a command - if (!isCommand) await automodHandler.performAutomod(message, settings); + if (!isCommand && !settings.chatbotId) await automodHandler.performAutomod(message, settings); };