diff --git a/config.js b/config.js index 6c319b1d2..677f90918 100644 --- a/config.js +++ b/config.js @@ -2,6 +2,12 @@ module.exports = { OWNER_IDS: [], // Bot owner ID's PREFIX: "!", // Default prefix for the bot SUPPORT_SERVER: "", // Your bot support server + PRESENCE: { + ENABLED: true, // Whether or not the bot should update its status + STATUS: "online", // The bot's status [online, idle, dnd, invisible] + TYPE: "WATCHING", // Status type for the bot [PLAYING | LISTENING | WATCHING | COMPETING] + MESSAGE: "{members} members in {servers} servers", // Your bot status message + }, DASHBOARD: { enabled: false, // enable or disable dashboard baseURL: "http://localhost:8080", // base url @@ -24,6 +30,8 @@ module.exports = { ECONOMY: { CURRENCY: "ā‚Ŗ", DAILY_COINS: 100, // coins to be received by daily command + MIN_BEG_AMOUNT: 100, // minimum coins to be received when beg command is used + MAX_BEG_AMOUNT: 2500, // maximum coins to be received when beg command is used }, IMAGE: { BASE_API: "https://image-api.strangebot.xyz", @@ -33,19 +41,11 @@ module.exports = { MAX_SEARCH_RESULTS: 5, NODES: [ { - host: "disbotlistlavalink.ml", - port: 443, - password: "LAVA", - identifier: "DBL Lavalink", - retryDelay: 3000, - secure: true, - }, - { - host: "lava.link", + host: "lavalink.strangebot.xyz", port: 80, - password: "anything as a password", - identifier: "Something Host", - retryDelay: 3000, + password: "strangebot", + identifier: "Strange Link", + retryDelay: 5000, secure: false, }, ], @@ -60,8 +60,8 @@ module.exports = { AUTOMOD: "#36393F", TICKET_CREATE: "#068ADD", TICKET_CLOSE: "#068ADD", - MUTE_LOG: "#102027", - UNMUTE_LOG: "#4B636E", + TIMEOUT_LOG: "#102027", + UNTIMEOUT_LOG: "#4B636E", KICK_LOG: "#FF7961", SOFTBAN_LOG: "#AF4448", BAN_LOG: "#D32F2F", @@ -71,6 +71,7 @@ module.exports = { UNDEAFEN_LOG: "#4B636E", DISCONNECT_LOG: "RANDOM", MOVE_LOG: "RANDOM", + GIVEAWAYS: "#FF468A", }, /* Maximum number of keys that can be stored */ CACHE_SIZE: { diff --git a/package.json b/package.json index e9b387bf0..31b3b8b3e 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "discord-js-bot", - "version": "4.0.1", + "version": "4.1.0", "description": "A multipurpose discord bot built using discord-js", "main": "bot.js", "author": "Sai Teja M", "license": "ISC", + "engines": { + "node": ">=16.6.0" + }, "scripts": { "dev": "nodemon .", "start": "node ." @@ -20,23 +23,23 @@ "btoa": "^1.2.1", "chalk": "^4.1.2", "country-language": "^0.1.7", - "discord.js": "^13.3.1", - "dotenv": "^10.0.0", + "discord-giveaways": "^5.0.1", + "discord.js": "^13.6.0", + "dotenv": "^16.0.0", "ejs": "^3.1.6", "erela.js": "^2.3.3", "erela.js-deezer": "^1.0.7", "erela.js-facebook": "^1.0.4", "erela.js-spotify": "^1.2.0", - "express": "^4.17.1", + "express": "^4.17.2", "express-session": "^1.17.2", "fixedsize-map": "^1.0.1", - "fs": "0.0.2", - "iso-639-1": "^2.1.10", + "fs": "0.0.1-security", + "iso-639-1": "^2.1.12", "module-alias": "^2.2.2", "moment": "^2.29.1", "mongoose": "^5.13.7", - "mongoose-lean-defaults": "^2.0.1", - "nekos.life": "^2.0.7", + "nekos.life": "^2.0.9", "node-fetch": "^2.6.1", "os": "^0.1.2", "outdent": "^0.8.0", @@ -45,16 +48,16 @@ "snakecord": "^1.0.9", "sourcebin_js": "^0.0.3-ignore", "string-progressbar": "^1.0.4", - "table": "^6.7.3", + "table": "^6.8.0", "timestamp-to-date": "^1.1.0", "twemoji-parser": "^13.1.0" }, "devDependencies": { - "eslint": "^7.32.0", - "eslint-plugin-jsdoc": "^36.0.8", + "eslint": "^8.6.0", + "eslint-plugin-jsdoc": "^37.6.1", "node": "^16.10.0", - "nodemon": "^2.0.12", - "prettier": "2.3.2" + "nodemon": "^2.0.15", + "prettier": "2.5.1" }, "keywords": [ "discord", diff --git a/src/commands/admin/automod/automodconfig.js b/src/commands/admin/automod/automodconfig.js index a07d51df2..cf831b153 100644 --- a/src/commands/admin/automod/automodconfig.js +++ b/src/commands/admin/automod/automodconfig.js @@ -1,5 +1,4 @@ const { Command } = require("@src/structures"); -const { getRoleByName } = require("@utils/guildUtils"); const { Message, MessageEmbed, CommandInteraction } = require("discord.js"); const { EMBED_COLORS } = require("@root/config.js"); const { getSettings } = require("@schemas/Guild"); @@ -216,13 +215,8 @@ async function setStrikes(settings, strikes) { async function setAction(settings, guild, action) { if (action === "MUTE") { - let mutedRole = getRoleByName(guild, "muted"); - if (!mutedRole) { - return `Muted role doesn't exist in this guild`; - } - - if (!mutedRole.editable) { - return "I do not have permission to move members to `Muted` role. Is that role below my highest role?"; + if (!guild.me.permissions.has("MODERATE_MEMBERS")) { + return "I do not permission to timeout members"; } } diff --git a/src/commands/admin/autorole.js b/src/commands/admin/autorole.js new file mode 100644 index 000000000..cb3ab3a9e --- /dev/null +++ b/src/commands/admin/autorole.js @@ -0,0 +1,118 @@ +const { Command } = require("@src/structures"); +const { getSettings } = require("@schemas/Guild"); +const { Message, CommandInteraction } = require("discord.js"); +const { findMatchingRoles } = require("@utils/guildUtils"); + +module.exports = class AutoRole extends Command { + constructor(client) { + super(client, { + name: "autorole", + description: "setup role to be given when a member joins the server", + category: "ADMIN", + userPermissions: ["MANAGE_GUILD"], + command: { + enabled: true, + usage: "", + minArgsCount: 1, + }, + slashCommand: { + enabled: true, + ephemeral: true, + options: [ + { + name: "add", + description: "setup the autorole", + type: "SUB_COMMAND", + options: [ + { + name: "role", + description: "the role to be given", + type: "ROLE", + required: false, + }, + { + name: "role_id", + description: "the role id to be given", + type: "STRING", + required: false, + }, + ], + }, + { + name: "remove", + description: "disable the autorole", + type: "SUB_COMMAND", + }, + ], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const input = args.join(" "); + let response; + + if (input.toLowerCase() === "off") { + response = await setAutoRole(message, null); + } else { + const roles = findMatchingRoles(message.guild, input); + if (roles.length === 0) response = "No matching roles found matching your query"; + else response = await setAutoRole(message, roles[0]); + } + + await message.reply(response); + } + + /** + * @param {CommandInteraction} interaction + */ + async interactionRun(interaction) { + const sub = interaction.options.getSubcommand(); + let response; + + // add + if (sub === "add") { + let role = interaction.options.getRole("role"); + if (!role) { + const role_id = interaction.options.getString("role_id"); + if (!role_id) return interaction.followUp("Please provide a role or role id"); + + const roles = findMatchingRoles(interaction.guild, role_id); + if (roles.length === 0) return interaction.followUp("No matching roles found matching your query"); + role = roles[0]; + } + + response = await setAutoRole(interaction, role); + } + + // remove + else if (sub === "remove") { + response = await setAutoRole(interaction, null); + } + + // default + else response = "Invalid subcommand"; + + await interaction.followUp(response); + } +}; + +async function setAutoRole({ guild }, role) { + const settings = await getSettings(guild); + + if (role) { + if (!guild.me.permissions.has("MANAGE_ROLES")) return "I don't have the `MANAGE_ROLES` permission"; + if (guild.me.roles.highest.position < role.position) return "I don't have the permissions to assign this role"; + if (role.managed) return "Oops! This role is managed by an integration"; + } + + if (!role) settings.autorole = null; + else settings.autorole = role.id; + + await settings.save(); + return `Configuration saved! Autorole is ${!role ? "disabled" : "setup"}`; +} diff --git a/src/commands/admin/counter-setup.js b/src/commands/admin/counter-setup.js index d4011557b..09ba07bea 100644 --- a/src/commands/admin/counter-setup.js +++ b/src/commands/admin/counter-setup.js @@ -56,8 +56,8 @@ module.exports = class CounterSetup extends Command { * @param {string[]} args */ async messageRun(message, args) { - const type = args[0].toLowerCase(); - if (!type || !["users", "members", "bots"].includes(type)) { + const type = args[0].toUpperCase(); + if (!type || !["USERS", "MEMBERS", "BOTS"].includes(type)) { return message.reply("Incorrect arguments are passed! Counter types: `users/members/bots`"); } if (args.length < 2) return message.reply("Incorrect Usage! You did not provide name"); @@ -75,7 +75,7 @@ module.exports = class CounterSetup extends Command { const type = interaction.options.getString("type"); const name = interaction.options.getString("name"); - const response = await setupCounter(interaction.guild, type, name); + const response = await setupCounter(interaction.guild, type.toUpperCase(), name); return interaction.followUp(response); } }; @@ -84,9 +84,9 @@ async function setupCounter(guild, type, name) { let channelName = name; const stats = await getMemberStats(guild); - if (type.toUpperCase() === "USERS") channelName += ` : ${stats[0]}`; - else if (type.toUpperCase() === "MEMBERS") channelName += ` : ${stats[2]}`; - else if (type.toUpperCase() === "BOTS") channelName += ` : ${stats[1]}`; + if (type === "USERS") channelName += ` : ${stats[0]}`; + else if (type === "MEMBERS") channelName += ` : ${stats[2]}`; + else if (type === "BOTS") channelName += ` : ${stats[1]}`; const vc = await guild.channels.create(channelName, { type: "GUILD_VOICE", @@ -104,7 +104,7 @@ async function setupCounter(guild, type, name) { const settings = await getSettings(guild); - const exists = settings.counters.find((v) => v.counter_type === type); + const exists = settings.counters.find((v) => v.counter_type.toUpperCase() === type); if (exists) { exists.name = name; exists.channel_id = vc.id; diff --git a/src/commands/admin/maxwarn.js b/src/commands/admin/maxwarn.js index 4d9649163..dedeabd67 100644 --- a/src/commands/admin/maxwarn.js +++ b/src/commands/admin/maxwarn.js @@ -1,7 +1,6 @@ const { Command } = require("@src/structures"); const { getSettings } = require("@schemas/Guild"); const { Message, CommandInteraction } = require("discord.js"); -const { getRoleByName } = require("@utils/guildUtils"); module.exports = class MaxWarn extends Command { constructor(client) { @@ -126,13 +125,8 @@ async function setLimit(guild, limit) { async function setAction(guild, action) { if (action === "MUTE") { - let mutedRole = getRoleByName(guild, "muted"); - if (!mutedRole) { - return `Muted role doesn't exist in this guild`; - } - - if (!mutedRole.editable) { - return "I do not have permission to move members to `Muted` role. Is that role below my highest role?"; + if (!guild.me.permissions.has("MODERATE_MEMBERS")) { + return "I do not permission to timeout members"; } } diff --git a/src/commands/admin/ticket/ticket.js b/src/commands/admin/ticket/ticket.js index 6e5ea3296..d7f17677b 100644 --- a/src/commands/admin/ticket/ticket.js +++ b/src/commands/admin/ticket/ticket.js @@ -114,7 +114,7 @@ module.exports = class Ticket extends Command { { name: "amount", description: "max number of tickets", - type: "STRING", + type: "INTEGER", required: true, }, ], @@ -308,9 +308,9 @@ async function runInteractiveSetup({ channel, guild, author }) { const filter = (m) => m.author.id === author.id; const embed = new MessageEmbed() - .setAuthor("Ticket Setup") + .setAuthor({ name: "Ticket Setup" }) .setColor(EMBED_COLORS.BOT_EMBED) - .setFooter("Type cancel to cancel setup"); + .setFooter({ text: "Type cancel to cancel setup" }); let targetChannel; let title; @@ -369,9 +369,9 @@ async function runInteractiveSetup({ channel, guild, author }) { async function setupTicket(guild, channel, title, role, color) { try { const embed = new MessageEmbed() - .setAuthor("Support Ticket") + .setAuthor({ name: "Support Ticket" }) .setDescription(title) - .setFooter("You can only have 1 open ticket at a time!"); + .setFooter({ text: "You can only have 1 open ticket at a time!" }); if (color) embed.setColor(color); diff --git a/src/commands/anime/nsfw.js b/src/commands/anime/nsfw.js index ebdc60559..159004b8b 100644 --- a/src/commands/anime/nsfw.js +++ b/src/commands/anime/nsfw.js @@ -88,11 +88,14 @@ const genNSFW = async (category, user) => { if (!category) category = "randomHentaiGif"; try { const response = await neko.nsfw[category](); - return new MessageEmbed().setImage(response.url).setColor("RANDOM").setFooter(`Requested by ${user.tag}`); + return new MessageEmbed() + .setImage(response.url) + .setColor("RANDOM") + .setFooter({ text: `Requested by ${user.tag}` }); } catch (ex) { return new MessageEmbed() .setColor(EMBED_COLORS.ERROR) .setDescription("Failed to fetch meme. Try again!") - .setFooter(`Requested by ${user.tag}`); + .setFooter({ text: `Requested by ${user.tag}` }); } }; diff --git a/src/commands/anime/react.js b/src/commands/anime/react.js index bb63c8509..60934290e 100644 --- a/src/commands/anime/react.js +++ b/src/commands/anime/react.js @@ -75,11 +75,14 @@ const genReaction = async (category, user) => { imageUrl = (await neko.sfw[category]()).url; } - return new MessageEmbed().setImage(imageUrl).setColor("RANDOM").setFooter(`Requested By ${user.tag}`); + return new MessageEmbed() + .setImage(imageUrl) + .setColor("RANDOM") + .setFooter({ text: `Requested By ${user.tag}` }); } catch (ex) { return new MessageEmbed() .setColor(EMBED_COLORS.ERROR) .setDescription("Failed to fetch meme. Try again!") - .setFooter(`Requested By ${user.tag}`); + .setFooter({ text: `Requested By ${user.tag}` }); } }; diff --git a/src/commands/economy/bank.js b/src/commands/economy/bank.js index 388b33aa5..277a300b8 100644 --- a/src/commands/economy/bank.js +++ b/src/commands/economy/bank.js @@ -155,7 +155,8 @@ module.exports = class BankCommand extends Command { // balance if (sub === "balance") { - response = await balance(interaction.user); + const user = interaction.options.getUser("user") || interaction.user; + response = await balance(user); } // deposit @@ -174,7 +175,7 @@ module.exports = class BankCommand extends Command { else if (sub === "transfer") { const user = interaction.options.getUser("user"); const coins = interaction.options.getInteger("coins"); - response = await transfer(interaction, user, coins); + response = await transfer(interaction.user, user, coins); } await interaction.followUp(response); diff --git a/src/commands/economy/beg.js b/src/commands/economy/beg.js new file mode 100644 index 000000000..afae4b733 --- /dev/null +++ b/src/commands/economy/beg.js @@ -0,0 +1,85 @@ +const { Command } = require("@src/structures"); +const { MessageEmbed, Message, CommandInteraction } = require("discord.js"); +const { getUser } = require("@schemas/User"); +const { EMBED_COLORS, ECONOMY } = require("@root/config.js"); + +module.exports = class BegCommand extends Command { + constructor(client) { + super(client, { + name: "beg", + description: "beg from someone", + category: "ECONOMY", + cooldown: 21600, + botPermissions: ["EMBED_LINKS"], + command: { + enabled: true, + }, + slashCommand: { + enabled: true, + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message) { + const response = await beg(message.author); + await message.reply(response); + } + + /** + * @param {CommandInteraction} interaction + */ + async interactionRun(interaction) { + const response = await beg(interaction.user); + await interaction.followUp(response); + } +}; + +async function beg(user) { + let users = [ + "PewDiePie", + "T-Series", + "Sans", + "RLX", + "Pro Gamer 711", + "Zenitsu", + "Jake Paul", + "Kaneki Ken", + "KSI", + "Naruto", + "Mr. Beast", + "Ur Mom", + "A Broke Person", + "Giyu Tomiaka", + "Bejing Embacy", + "A Random Asian Mom", + "Ur Step Sis", + "Jin Mori", + "Sakura (AKA Trash Can)", + "Hammy The Hamster", + "Kakashi Sensei", + "Minato", + "Tanjiro", + "ZHC", + "The IRS", + "Joe Mama", + ]; + + let amount = Math.floor(Math.random() * `${ECONOMY.MAX_BEG_AMOUNT}` + `${ECONOMY.MIN_BEG_AMOUNT}`); + const userDb = await getUser(user.id); + userDb.coins += amount; + await userDb.save(); + + const embed = new MessageEmbed() + .setColor(EMBED_COLORS.BOT_EMBED) + .setAuthor({ name: `${user.username}`, iconURL: user.displayAvatarURL() }) + .setDescription( + `**${users[Math.floor(Math.random() * users.length)]}** donated you **${amount}** ${ECONOMY.CURRENCY}\n` + + `**Updated Balance:** **${userDb.coins}** ${ECONOMY.CURRENCY}` + ); + + return { embeds: [embed] }; +} diff --git a/src/commands/economy/daily.js b/src/commands/economy/daily.js index 5fe2ee210..217bef500 100644 --- a/src/commands/economy/daily.js +++ b/src/commands/economy/daily.js @@ -61,7 +61,7 @@ async function daily(user) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor(user.username, user.displayAvatarURL()) + .setAuthor({ name: user.username, iconURL: user.displayAvatarURL() }) .setDescription( `You got ${ECONOMY.DAILY_COINS}${ECONOMY.CURRENCY} as your daily reward\n` + `**Updated Balance:** ${userDb.coins}${ECONOMY.CURRENCY}` diff --git a/src/commands/economy/gamble.js b/src/commands/economy/gamble.js index 59a365aaf..e2d07d3d7 100644 --- a/src/commands/economy/gamble.js +++ b/src/commands/economy/gamble.js @@ -117,11 +117,11 @@ async function gamble(user, betAmount) { await userDb.save(); const embed = new MessageEmbed() - .setAuthor(user.username, user.displayAvatarURL()) + .setAuthor({ name: user.username, iconURL: user.displayAvatarURL() }) .setColor(EMBED_COLORS.TRANSPARENT) .setThumbnail("https://i.pinimg.com/originals/9a/f1/4e/9af14e0ae92487516894faa9ea2c35dd.gif") .setDescription(str) - .setFooter(`${result}\nUpdated Wallet balance: ${userDb?.coins}${ECONOMY.CURRENCY}`); + .setFooter({ text: `${result}\nUpdated Wallet balance: ${userDb?.coins}${ECONOMY.CURRENCY}` }); return { embeds: [embed] }; } diff --git a/src/commands/economy/sub/balance.js b/src/commands/economy/sub/balance.js index 9652ceaed..5fd7124e9 100644 --- a/src/commands/economy/sub/balance.js +++ b/src/commands/economy/sub/balance.js @@ -7,7 +7,7 @@ module.exports = async (user) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor(user.username) + .setAuthor({ name: user.username }) .setThumbnail(user.displayAvatarURL()) .addField("Wallet", `${economy?.coins || 0}${ECONOMY.CURRENCY}`, true) .addField("Bank", `${economy?.bank || 0}${ECONOMY.CURRENCY}`, true) diff --git a/src/commands/economy/sub/deposit.js b/src/commands/economy/sub/deposit.js index a430a5538..c76d950ac 100644 --- a/src/commands/economy/sub/deposit.js +++ b/src/commands/economy/sub/deposit.js @@ -14,7 +14,7 @@ module.exports = async (user, coins) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor("New Balance") + .setAuthor({ name: "New Balance" }) .setThumbnail(user.displayAvatarURL()) .addField("Wallet", `${userDb.coins}${ECONOMY.CURRENCY}`, true) .addField("Bank", `${userDb.bank}${ECONOMY.CURRENCY}`, true) diff --git a/src/commands/economy/sub/transfer.js b/src/commands/economy/sub/transfer.js index 7d030ea73..b933209c3 100644 --- a/src/commands/economy/sub/transfer.js +++ b/src/commands/economy/sub/transfer.js @@ -9,25 +9,24 @@ module.exports = async (self, target, coins) => { const userDb = await getUser(self.id); - if (userDb.coins < coins) { - return `Insufficient coin balance! You only have ${userDb.coins}${ECONOMY.CURRENCY}`; + if (userDb.bank < coins) { + return `Insufficient bank balance! You only have ${userDb.bank}${ECONOMY.CURRENCY} in your bank account.${ + userDb.coins > 0 && "\nYou must deposit your coins in bank before you can transfer" + } `; } const targetDb = await getUser(target.id); - userDb.coins -= coins; - targetDb.coins += coins; + userDb.bank -= coins; + targetDb.bank += coins; await userDb.save(); await targetDb.save(); const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor("Updated Balance") - .setDescription( - `**${self.username}:** ${userDb.coins}${ECONOMY.CURRENCY}\n` + - `**${target.username}:** ${targetDb.coins}${ECONOMY.CURRENCY}` - ) + .setAuthor({ name: "Updated Balance" }) + .setDescription(`You have successfully transferred ${coins}${ECONOMY.CURRENCY} to ${target.tag}`) .setTimestamp(Date.now()); return { embeds: [embed] }; diff --git a/src/commands/economy/sub/withdraw.js b/src/commands/economy/sub/withdraw.js index 0ff5b71db..38946bbaa 100644 --- a/src/commands/economy/sub/withdraw.js +++ b/src/commands/economy/sub/withdraw.js @@ -14,7 +14,7 @@ module.exports = async (user, coins) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor("New Balance") + .setAuthor({ name: "New Balance" }) .setThumbnail(user.displayAvatarURL()) .addField("Wallet", `${userDb.coins}${ECONOMY.CURRENCY}`, true) .addField("Bank", `${userDb.bank}${ECONOMY.CURRENCY}`, true) diff --git a/src/commands/fun/animal.js b/src/commands/fun/animal.js index d651c6032..2a2799519 100644 --- a/src/commands/fun/animal.js +++ b/src/commands/fun/animal.js @@ -65,7 +65,7 @@ async function getAnimal(user, choice) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.TRANSPARENT) .setImage(imageUrl) - .setFooter(`Requested by ${user.tag}`); + .setFooter({ text: `Requested by ${user.tag}` }); return { embeds: [embed] }; } diff --git a/src/commands/fun/facts.js b/src/commands/fun/facts.js index 1cee7933c..90aa6b05f 100644 --- a/src/commands/fun/facts.js +++ b/src/commands/fun/facts.js @@ -68,7 +68,7 @@ async function getFact(user, choice) { .setColor(EMBED_COLORS.TRANSPARENT) .setThumbnail(imageUrl) .setDescription(fact) - .setFooter(`Requested by ${user.tag}`); + .setFooter({ text: `Requested by ${user.tag}` }); return { embeds: [embed] }; } diff --git a/src/commands/fun/meme.js b/src/commands/fun/meme.js index 5432ffa02..0c82a9fd1 100644 --- a/src/commands/fun/meme.js +++ b/src/commands/fun/meme.js @@ -139,10 +139,10 @@ async function getRandomEmbed(choice) { let memeNumComments = json[0].data.children[0].data.num_comments; return new MessageEmbed() - .setAuthor(memeTitle, null, memeUrl) + .setAuthor({ name: memeTitle, url: memeUrl }) .setImage(memeImage) .setColor("RANDOM") - .setFooter(`šŸ‘ ${memeUpvotes} | šŸ’¬ ${memeNumComments}`); + .setFooter({ text: `šŸ‘ ${memeUpvotes} | šŸ’¬ ${memeNumComments}` }); } catch (error) { return new MessageEmbed().setColor(EMBED_COLORS.ERROR).setDescription("Failed to fetch meme. Try again!"); } diff --git a/src/commands/giveaways/giveaway.js b/src/commands/giveaways/giveaway.js new file mode 100644 index 000000000..af60dff79 --- /dev/null +++ b/src/commands/giveaways/giveaway.js @@ -0,0 +1,470 @@ +const { Command } = require("@src/structures"); +const { Message, CommandInteraction, MessageEmbed } = require("discord.js"); +const { parsePermissions } = require("@utils/botUtils"); +const { EMBED_COLORS } = require("@root/config"); + +// Sub Commands +const start = require("./sub/start"); +const pause = require("./sub/pause"); +const resume = require("./sub/resume"); +const end = require("./sub/end"); +const reroll = require("./sub/reroll"); +const list = require("./sub/list"); +const edit = require("./sub/edit"); + +module.exports = class Giveaway extends Command { + constructor(client) { + super(client, { + name: "giveaway", + description: "giveaway commands", + category: "GIVEAWAY", + command: { + enabled: true, + minArgsCount: 1, + subcommands: [ + { + trigger: "start", + description: "start an interactive giveaway setup", + }, + { + trigger: "pause ", + description: "pause a giveaway", + }, + { + trigger: "resume ", + description: "resume a paused giveaway", + }, + { + trigger: "end ", + description: "end a giveaway", + }, + { + trigger: "reroll ", + description: "reroll a giveaway", + }, + { + trigger: "list", + description: "list all giveaways", + }, + { + trigger: "edit", + description: "edit a giveaway", + }, + ], + }, + slashCommand: { + enabled: true, + ephemeral: true, + options: [ + { + name: "start", + description: "start a giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "channel", + description: "the channel to start the giveaway in", + type: "CHANNEL", + channelTypes: ["GUILD_TEXT"], + required: true, + }, + { + name: "duration", + description: "the duration of the giveaway in minutes", + type: "INTEGER", + required: true, + }, + { + name: "prize", + description: "the prize of the giveaway", + type: "STRING", + required: true, + }, + { + name: "winners", + description: "the number of winners", + type: "INTEGER", + required: true, + }, + { + name: "host", + description: "the host of the giveaway", + type: "USER", + required: false, + }, + ], + }, + { + name: "pause", + description: "pause a giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "message_id", + description: "the message id of the giveaway", + type: "STRING", + required: true, + }, + ], + }, + { + name: "resume", + description: "resume a paused giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "message_id", + description: "the message id of the giveaway", + type: "STRING", + required: true, + }, + ], + }, + { + name: "end", + description: "end a giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "message_id", + description: "the message id of the giveaway", + type: "STRING", + required: true, + }, + ], + }, + { + name: "reroll", + description: "reroll a giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "message_id", + description: "the message id of the giveaway", + type: "STRING", + required: true, + }, + ], + }, + { + name: "list", + description: "list all giveaways", + type: "SUB_COMMAND", + }, + { + name: "edit", + description: "edit a giveaway", + type: "SUB_COMMAND", + options: [ + { + name: "message_id", + description: "the message id of the giveaway", + type: "STRING", + required: true, + }, + { + name: "add_duration", + description: "the number of minutes to add to the giveaway duration", + type: "INTEGER", + required: false, + }, + { + name: "new_prize", + description: "the new prize", + type: "STRING", + required: false, + }, + { + name: "new_winners", + description: "the new number of winners", + type: "INTEGER", + required: false, + }, + ], + }, + ], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const sub = args[0]?.toLowerCase(); + let response; + + // + if (sub === "start") { + return await runInteractiveSetup(message); + } + + // + else if (sub === "pause") { + const messageId = args[1]; + response = await pause(message.member, messageId); + } + + // + else if (sub === "resume") { + const messageId = args[1]; + response = await resume(message.member, messageId); + } + + // + else if (sub === "end") { + const messageId = args[1]; + response = await end(message.member, messageId); + } + + // + else if (sub === "reroll") { + const messageId = args[1]; + response = await reroll(message.member, messageId); + } + + // + else if (sub === "list") { + response = await list(message.member); + } + + // + else if (sub === "edit") { + return await runInteractiveEdit(message); + } + + // + else response = "Not a valid sub command"; + + await message.reply(response); + } + + /** + * @param {CommandInteraction} interaction + */ + async interactionRun(interaction) { + const sub = interaction.options.getSubcommand(); + let response; + + // + if (sub === "start") { + const channel = interaction.options.getChannel("channel"); + const duration = interaction.options.getInteger("duration"); + const prize = interaction.options.getString("prize"); + const winnerCount = interaction.options.getInteger("winners"); + const host = interaction.options.getUser("host"); + response = await start(interaction.member, channel, duration, prize, winnerCount, host); + } + + // + else if (sub === "pause") { + const messageId = interaction.options.getString("message_id"); + response = await pause(interaction.member, messageId); + } + + // + else if (sub === "resume") { + const messageId = interaction.options.getString("message_id"); + response = await resume(interaction.member, messageId); + } + + // + else if (sub === "end") { + const messageId = interaction.options.getString("message_id"); + response = await end(interaction.member, messageId); + } + + // + else if (sub === "reroll") { + const messageId = interaction.options.getString("message_id"); + response = await reroll(interaction.member, messageId); + } + + // + else if (sub === "list") { + response = await list(interaction.member); + } + + // + else if (sub === "edit") { + const messageId = interaction.options.getString("message_id"); + const addDuration = interaction.options.getInteger("add_duration"); + const newPrize = interaction.options.getString("new_prize"); + const newWinnerCount = interaction.options.getInteger("new_winners"); + response = await edit(interaction.member, messageId, addDuration, newPrize, newWinnerCount); + } + + // + else response = "Invalid subcommand"; + + await interaction.followUp(response); + } +}; + +// Interactive Giveaway setup +async function runInteractiveSetup(message) { + const { member, channel, guild } = message; + + const SETUP_TIMEOUT = 30 * 1000; + const SETUP_PERMS = ["VIEW_CHANNEL", "SEND_MESSAGES", "EMBED_LINKS"]; + + const filter = (m) => m.author.id === member.id; + const embed = new MessageEmbed() + .setAuthor({ name: "Giveaway Setup" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setFooter({ text: "Type cancel to cancel setup" }); + + let targetChannel; + let duration; + let prize; + let winners = 1; + let host; + + const waitFor = async (question) => { + await channel.send({ embeds: [embed.setDescription(question)] }); + try { + let reply = (await channel.awaitMessages({ filter, max: 1, time: SETUP_TIMEOUT, errors: ["time"] })).first(); + if (reply.content.toLowerCase() === "cancel") { + await reply.reply("Giveaway setup has been cancelled"); + return false; + } + + return reply; + } catch (err) { + await channel.send({ embeds: [embed.setDescription("No response received. Giveaway setup has been cancelled")] }); + return false; + } + }; + + let reply; + try { + // wait for channel + reply = await waitFor("Please `mention the channel` in which the giveaway must be hosted"); + if (reply === false) return; + targetChannel = reply.mentions.channels.first(); + if (!targetChannel) return reply.reply("Giveaway setup has been cancelled. You did not mention a channel"); + if (!targetChannel.isText() && !targetChannel.permissionsFor(guild.me).has(SETUP_PERMS)) { + return reply.reply( + `Giveaway setup has been cancelled.\nI need ${parsePermissions(SETUP_PERMS)} in ${targetChannel}` + ); + } + + // wait for duration + reply = await waitFor("Please `specify the duration` of the giveaway in minutes"); + if (reply === false) return; + duration = reply.content; + if (isNaN(duration)) return reply.reply("Giveaway setup has been cancelled. You did not specify a duration"); + + // wait for prize + reply = await waitFor("Please `specify the prize` of the giveaway"); + if (reply === false) return; + prize = reply.content; + + // winner count + reply = await waitFor("Please `specify the number of winners`"); + if (reply === false) return; + winners = reply.content; + if (isNaN(winners)) { + return reply.reply("Giveaway setup has been cancelled. You did not specify a number of winners"); + } + winners = parseInt(winners); + + // host + reply = await waitFor( + "Please `mention the user` that will host the giveaway. Alternatively, you can specify `none`" + ); + if (reply === false) return; + host = reply.mentions.users.first(); + if (!host) host = null; + + // wait for confirmation + reply = await waitFor( + `Are you sure you want to host a giveaway in ${targetChannel} for ${duration} minutes with ${prize} for ${winners} winners?\n\n Type \`yes\` to confirm` + ); + if (reply === false) return; + if (reply.content.toLowerCase() !== "yes") { + return reply.reply("Giveaway setup has been cancelled"); + } + + const response = await start(message.member, targetChannel, duration, prize, winners, host); + await channel.send(response); + } catch (e) { + await channel.send({ embeds: [embed.setDescription("An error occurred while setting up the giveaway")] }); + } +} + +// Interactive Giveaway Update +async function runInteractiveEdit(message) { + const { member, channel } = message; + + const SETUP_TIMEOUT = 30 * 1000; + + const filter = (m) => m.author.id === member.id; + const embed = new MessageEmbed() + .setAuthor({ name: "Giveaway Update" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setFooter({ text: "Type `cancel` to cancel update!\nType `skip` to skip this step" }); + + let messageId; + let addDuration; + let newPrize; + let newWinnerCount; + + const waitFor = async (question) => { + await channel.send({ embeds: [embed.setDescription(question)] }); + try { + let reply = (await channel.awaitMessages({ filter, max: 1, time: SETUP_TIMEOUT, errors: ["time"] })).first(); + if (reply.content.toLowerCase() === "cancel") { + await reply.reply("Giveaway update has been cancelled"); + return false; + } + + return reply; + } catch (err) { + await channel.send({ + embeds: [embed.setDescription("No response received. Giveaway update has been cancelled")], + }); + return false; + } + }; + + let reply; + try { + // wait for messageId + reply = await waitFor("Please enter the `messageId` of the existing giveaway"); + if (reply === false) return; + messageId = reply.content; + + // wait for addDuration + reply = await waitFor("Please specify the `duration to add` to the giveaway in minutes"); + if (reply === false) return; + if (reply.content.toLowerCase() !== "skip") { + addDuration = reply.content; + if (isNaN(addDuration)) return reply.reply("Giveaway update has been cancelled. You did not specify a duration"); + } + + // wait for new prize + reply = await waitFor("Please specify the `new prize` of the giveaway"); + if (reply.content.toLowerCase() !== "skip") { + if (reply === false) return; + newPrize = reply.content; + } + + // winner count + reply = await waitFor("Please specify the `new number of winners`"); + if (reply === false) return; + if (reply.content.toLowerCase() !== "skip") { + newWinnerCount = reply.content; + if (isNaN(newWinnerCount)) { + return reply.reply("Giveaway setup has been cancelled. You did not specify a number of winners"); + } + newWinnerCount = parseInt(newWinnerCount); + } + + const response = await edit(message.member, messageId, addDuration, newPrize, newWinnerCount); + await channel.send(response); + } catch (e) { + await channel.send({ embeds: [embed.setDescription("An error occurred while setting up the giveaway")] }); + } +} diff --git a/src/commands/giveaways/sub/edit.js b/src/commands/giveaways/sub/edit.js new file mode 100644 index 000000000..463167c65 --- /dev/null +++ b/src/commands/giveaways/sub/edit.js @@ -0,0 +1,36 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {string} messageId + * @param {number} addDuration + * @param {string} newPrize + * @param {number} newWinnerCount + */ +module.exports = async (member, messageId, addDuration, newPrize, newWinnerCount) => { + if (!messageId) return "You must provide a valid message id."; + + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to start giveaways."; + } + + // Search with messageId + const giveaway = member.client.giveawaysManager.giveaways.find( + (g) => g.messageId === messageId && g.guildId === member.guild.id + ); + + // If no giveaway was found + if (!giveaway) return `Unable to find a giveaway for messageId: ${messageId}`; + + try { + await member.client.giveawaysManager.edit(messageId, { + addTime: 60000 * addDuration || 0, + newPrize: newPrize || giveaway.prize, + newWinnerCount: newWinnerCount || giveaway.winnerCount, + }); + + return `Successfully updated the giveaway!`; + } catch (error) { + member.client.logger.error("Giveaway Edit", error); + return `An error occurred while updating the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/end.js b/src/commands/giveaways/sub/end.js new file mode 100644 index 000000000..669d8c13b --- /dev/null +++ b/src/commands/giveaways/sub/end.js @@ -0,0 +1,31 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {string} messageId + */ +module.exports = async (member, messageId) => { + if (!messageId) return "You must provide a valid message id."; + + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to start giveaways."; + } + + // Search with messageId + const giveaway = member.client.giveawaysManager.giveaways.find( + (g) => g.messageId === messageId && g.guildId === member.guild.id + ); + + // If no giveaway was found + if (!giveaway) return `Unable to find a giveaway for messageId: ${messageId}`; + + // Check if the giveaway is ended + if (giveaway.ended) return "The giveaway has already ended."; + + try { + await giveaway.end(); + return "Success! The giveaway has ended!"; + } catch (error) { + member.client.logger.error("Giveaway End", error); + return `An error occurred while ending the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/list.js b/src/commands/giveaways/sub/list.js new file mode 100644 index 000000000..470080360 --- /dev/null +++ b/src/commands/giveaways/sub/list.js @@ -0,0 +1,30 @@ +const { EMBED_COLORS } = require("@root/config"); + +/** + * @param {import('discord.js').GuildMember} member + */ +module.exports = async (member) => { + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to manage giveaways."; + } + + // Search with all giveaways + const giveaways = member.client.giveawaysManager.giveaways.filter( + (g) => g.guildId === member.guild.id && g.ended === false + ); + + // No giveaways + if (giveaways.length === 0) { + return "There are no giveaways running in this server."; + } + + const description = giveaways.map((g, i) => `${i + 1}. ${g.prize} in <#${g.channelId}>`).join("\n"); + + try { + return { embeds: [{ description, color: EMBED_COLORS.GIVEAWAYS }] }; + } catch (error) { + member.client.logger.error("Giveaway List", error); + return `An error occurred while listing the giveaways: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/pause.js b/src/commands/giveaways/sub/pause.js new file mode 100644 index 000000000..b0a3b6864 --- /dev/null +++ b/src/commands/giveaways/sub/pause.js @@ -0,0 +1,31 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {string} messageId + */ +module.exports = async (member, messageId) => { + if (!messageId) return "You must provide a valid message id."; + + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to manage giveaways."; + } + + // Search with messageId + const giveaway = member.client.giveawaysManager.giveaways.find( + (g) => g.messageId === messageId && g.guildId === member.guild.id + ); + + // If no giveaway was found + if (!giveaway) return `Unable to find a giveaway for messageId: ${messageId}`; + + // Check if the giveaway is paused + if (giveaway.pauseOptions.isPaused) return "This giveaway is already paused."; + + try { + await giveaway.pause(); + return "Success! Giveaway paused!"; + } catch (error) { + member.client.logger.error("Giveaway Pause", error); + return `An error occurred while pausing the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/reroll.js b/src/commands/giveaways/sub/reroll.js new file mode 100644 index 000000000..81024632e --- /dev/null +++ b/src/commands/giveaways/sub/reroll.js @@ -0,0 +1,31 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {string} messageId + */ +module.exports = async (member, messageId) => { + if (!messageId) return "You must provide a valid message id."; + + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to start giveaways."; + } + + // Search with messageId + const giveaway = member.client.giveawaysManager.giveaways.find( + (g) => g.messageId === messageId && g.guildId === member.guild.id + ); + + // If no giveaway was found + if (!giveaway) return `Unable to find a giveaway for messageId: ${messageId}`; + + // Check if the giveaway is ended + if (!giveaway.ended) return "The giveaway is not ended yet."; + + try { + await giveaway.reroll(); + return "Giveaway rerolled!"; + } catch (error) { + member.client.logger.error("Giveaway Reroll", error); + return `An error occurred while rerolling the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/resume.js b/src/commands/giveaways/sub/resume.js new file mode 100644 index 000000000..b6eb5dc1d --- /dev/null +++ b/src/commands/giveaways/sub/resume.js @@ -0,0 +1,31 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {string} messageId + */ +module.exports = async (member, messageId) => { + if (!messageId) return "You must provide a valid message id."; + + // Permissions + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to manage giveaways."; + } + + // Search with messageId + const giveaway = member.client.giveawaysManager.giveaways.find( + (g) => g.messageId === messageId && g.guildId === member.guild.id + ); + + // If no giveaway was found + if (!giveaway) return `Unable to find a giveaway for messageId: ${messageId}`; + + // Check if the giveaway is unpaused + if (!giveaway.pauseOptions.isPaused) return "This giveaway is not paused."; + + try { + await giveaway.unpause(); + return "Success! Giveaway unpaused!"; + } catch (error) { + member.client.logger.error("Giveaway Resume", error); + return `An error occurred while unpausing the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/giveaways/sub/start.js b/src/commands/giveaways/sub/start.js new file mode 100644 index 000000000..6a907be71 --- /dev/null +++ b/src/commands/giveaways/sub/start.js @@ -0,0 +1,39 @@ +/** + * @param {import('discord.js').GuildMember} member + * @param {import('discord.js').GuildTextBasedChannel} giveawayChannel + * @param {number} duration + * @param {string} prize + * @param {number} winners + * @param {import('discord.js').User} host + */ +module.exports = async (member, giveawayChannel, duration, prize, winners, host) => { + if (!member.permissions.has("MANAGE_MESSAGES")) { + return "You need to have the manage messages permissions to start giveaways."; + } + + if (!giveawayChannel.isText()) { + return "You can only start giveaways in text channels."; + } + + try { + await member.client.giveawaysManager.start(giveawayChannel, { + duration: 60000 * duration, + prize, + winnerCount: winners, + hostedBy: host, + thumbnail: "https://i.imgur.com/DJuTuxs.png", + messages: { + giveaway: "šŸŽ‰ **GIVEAWAY** šŸŽ‰", + giveawayEnded: "šŸŽ‰ **GIVEAWAY ENDED** šŸŽ‰", + inviteToParticipate: "React with šŸŽ to enter", + dropMessage: "Be the first to react with šŸŽ to win!", + hostedBy: "\nHosted by:", + }, + }); + + return `Giveaway started in ${giveawayChannel}`; + } catch (error) { + member.client.logger.error("Giveaway Start", error); + return `An error occurred while starting the giveaway: ${error.message}`; + } +}; diff --git a/src/commands/image/filters.js b/src/commands/image/filters.js index 775557562..b0a852892 100644 --- a/src/commands/image/filters.js +++ b/src/commands/image/filters.js @@ -62,7 +62,7 @@ module.exports = class Filters extends Command { const embed = new MessageEmbed() .setColor(EMBED_COLORS.TRANSPARENT) .setImage("attachment://attachment.png") - .setFooter(`Requested by: ${message.author.tag}`); + .setFooter({ text: `Requested by: ${message.author.tag}` }); await message.reply({ embeds: [embed], files: [attachment] }); } @@ -90,7 +90,7 @@ module.exports = class Filters extends Command { const embed = new MessageEmbed() .setColor(EMBED_COLORS.TRANSPARENT) .setImage("attachment://attachment.png") - .setFooter(`Requested by: ${author.tag}`); + .setFooter({ text: `Requested by: ${author.tag}` }); await interaction.followUp({ embeds: [embed], files: [attachment] }); } diff --git a/src/commands/image/generators.js b/src/commands/image/generators.js index 16a1359cc..1a9119491 100644 --- a/src/commands/image/generators.js +++ b/src/commands/image/generators.js @@ -87,7 +87,7 @@ module.exports = class Generator extends Command { const embed = new MessageEmbed() .setColor(EMBED_COLORS.TRANSPARENT) .setImage("attachment://attachment.png") - .setFooter(`Requested by: ${message.author.tag}`); + .setFooter({ text: `Requested by: ${message.author.tag}` }); await message.reply({ embeds: [embed], files: [attachment] }); } @@ -115,7 +115,7 @@ module.exports = class Generator extends Command { const embed = new MessageEmbed() .setColor(EMBED_COLORS.TRANSPARENT) .setImage("attachment://attachment.png") - .setFooter(`Requested by: ${author.tag}`); + .setFooter({ text: `Requested by: ${author.tag}` }); await interaction.followUp({ embeds: [embed], files: [attachment] }); } diff --git a/src/commands/information/avatar.js b/src/commands/information/avatar.js index dc7467968..89deecfc2 100644 --- a/src/commands/information/avatar.js +++ b/src/commands/information/avatar.js @@ -26,7 +26,7 @@ module.exports = class UserInfo extends Command { */ async messageRun(message, args) { const target = (args.length && (await resolveMember(message, args[0]))) || message.member; - const response = avatarInfo(target); + const response = avatarInfo(target.user); await message.reply(response); } }; diff --git a/src/commands/information/leaderboard.js b/src/commands/information/leaderboard.js index 95cdada7e..61b202e9e 100644 --- a/src/commands/information/leaderboard.js +++ b/src/commands/information/leaderboard.js @@ -2,7 +2,7 @@ const { Command } = require("@src/structures"); const { Message, MessageEmbed, CommandInteraction } = require("discord.js"); const { EMBED_COLORS } = require("@root/config"); const { getSettings } = require("@schemas/Guild"); -const { getTop100 } = require("@schemas/Member"); +const { getXpLb, getInvitesLb } = require("@schemas/Member"); module.exports = class LeaderBoard extends Command { constructor(client) { @@ -14,9 +14,29 @@ module.exports = class LeaderBoard extends Command { command: { enabled: true, aliases: ["lb"], + minArgsCount: 1, + usage: "", }, slashCommand: { enabled: true, + options: [ + { + name: "type", + description: "type of leaderboard to display", + required: true, + type: "STRING", + choices: [ + { + name: "xp", + value: "xp", + }, + { + name: "invite", + value: "invite", + }, + ], + }, + ], }, }); } @@ -26,7 +46,12 @@ module.exports = class LeaderBoard extends Command { * @param {string[]} args */ async messageRun(message, args) { - const response = await getLeaderboard(message, message.author); + const type = args[0].toLowerCase(); + let response; + + if (type === "xp") response = await getXpLeaderboard(message, message.author); + else if (type === "invite") response = await getInviteLeaderboard(message, message.author); + else response = "Invalid Leaderboard type. Choose either `xp` or `invite`"; await message.reply(response); } @@ -34,19 +59,24 @@ module.exports = class LeaderBoard extends Command { * @param {CommandInteraction} interaction */ async interactionRun(interaction) { - const response = await getLeaderboard(interaction, interaction.user); + const type = interaction.options.getString("type"); + let response; + + if (type === "xp") response = await getXpLeaderboard(interaction, interaction.user); + else if (type === "invite") response = await getInviteLeaderboard(interaction, interaction.user); + else response = "Invalid Leaderboard type. Choose either `xp` or `invite`"; + await interaction.followUp(response); } }; -async function getLeaderboard({ guild }, author) { +async function getXpLeaderboard({ guild }, author) { const settings = await getSettings(guild); if (!settings.ranking.enabled) return "Ranking is disabled on this server"; - const top100 = await getTop100(guild.id); - if (top100.length === 0) return "No users in the leaderboard"; + const lb = await getXpLb(guild.id, 10); + if (lb.length === 0) return "No users in the leaderboard"; - const lb = top100.splice(0, top100.length > 10 ? 9 : top100.length); let collector = ""; for (let i = 0; i < lb.length; i++) { try { @@ -58,10 +88,36 @@ async function getLeaderboard({ guild }, author) { } const embed = new MessageEmbed() - .setAuthor("XP Leaderboard") + .setAuthor({ name: "XP Leaderboard" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(collector) + .setFooter({ text: `Requested by ${author.tag}` }); + + return { embeds: [embed] }; +} + +async function getInviteLeaderboard({ guild }, author) { + const settings = await getSettings(guild); + if (!settings.invite.tracking) return "Invite tracking is disabled on this server"; + + const lb = await getInvitesLb(guild.id, 10); + if (lb.length === 0) return "No users in the leaderboard"; + + let collector = ""; + for (let i = 0; i < lb.length; i++) { + try { + const user = await author.client.users.fetch(lb[i].member_id); + collector += `**#${(i + 1).toString()}** - ${user.tag} [${lb[i].invites}]\n`; + } catch (ex) { + // Ignore + } + } + + const embed = new MessageEmbed() + .setAuthor({ name: "Invite Leaderboard" }) .setColor(EMBED_COLORS.BOT_EMBED) .setDescription(collector) - .setFooter(`Requested by ${author.tag}`); + .setFooter({ text: `Requested by ${author.tag}` }); return { embeds: [embed] }; } diff --git a/src/commands/information/rank.js b/src/commands/information/rank.js index c72c4ac68..178c9353c 100644 --- a/src/commands/information/rank.js +++ b/src/commands/information/rank.js @@ -4,7 +4,7 @@ const { EMBED_COLORS, IMAGE } = require("@root/config"); const { getBuffer } = require("@utils/httpUtils"); const { getSettings } = require("@schemas/Guild"); const { resolveMember } = require("@utils/guildUtils"); -const { getMember, getTop100 } = require("@schemas/Member"); +const { getMember, getXpLb } = require("@schemas/Member"); module.exports = class Rank extends Command { constructor(client) { @@ -62,7 +62,7 @@ async function getRank({ guild }, member) { const memberDb = await getMember(guild.id, user.id); if (!memberDb.xp) return `${user.tag} is not ranked yet!`; - const lb = await getTop100(guild.id); + const lb = await getXpLb(guild.id, 100); let pos = -1; lb.forEach((doc, i) => { if (doc.member_id == user.id) { diff --git a/src/commands/information/shared/botinvite.js b/src/commands/information/shared/botinvite.js index 2869debf9..d8c5b3a30 100644 --- a/src/commands/information/shared/botinvite.js +++ b/src/commands/information/shared/botinvite.js @@ -3,7 +3,7 @@ const { EMBED_COLORS, SUPPORT_SERVER, DASHBOARD } = require("@root/config"); module.exports = (client) => { const embed = new MessageEmbed() - .setAuthor("Invite") + .setAuthor({ name: "Invite" }) .setColor(EMBED_COLORS.BOT_EMBED) .setThumbnail(client.user.displayAvatarURL()) .setDescription("Hey there! Thanks for considering to invite me\nUse the button below to navigate where you want"); diff --git a/src/commands/information/shared/channel.js b/src/commands/information/shared/channel.js index 1779b1f14..5a585ccc2 100644 --- a/src/commands/information/shared/channel.js +++ b/src/commands/information/shared/channel.js @@ -58,7 +58,10 @@ module.exports = (channel) => { `; } - const embed = new MessageEmbed().setAuthor("Channel Details").setColor(EMBED_COLORS.BOT_EMBED).setDescription(desc); + const embed = new MessageEmbed() + .setAuthor({ name: "Channel Details" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(desc); return { embeds: [embed] }; }; diff --git a/src/commands/information/shared/emoji.js b/src/commands/information/shared/emoji.js index e82561dbe..3366b1ba0 100644 --- a/src/commands/information/shared/emoji.js +++ b/src/commands/information/shared/emoji.js @@ -9,7 +9,7 @@ module.exports = (emoji) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor("Emoji Info") + .setAuthor({ name: "Emoji Info" }) .setDescription( `**Id:** ${custom.id}\n` + `**Name:** ${custom.name}\n` + `**Animated:** ${custom.animated ? "Yes" : "No"}` ) diff --git a/src/commands/information/shared/profile.js b/src/commands/information/shared/profile.js index ded2316f2..778e20701 100644 --- a/src/commands/information/shared/profile.js +++ b/src/commands/information/shared/profile.js @@ -25,7 +25,7 @@ module.exports = async ({ guild }, user) => { .addField("Strikes*", memberData.strikes + " ", true) .addField("Warnings*", memberData.warnings + " ", true) .addField("Avatar-URL", user.displayAvatarURL({ format: "png" })) - .setFooter("Fields marked (*) are guild specific"); + .setFooter({ text: "Fields marked (*) are guild specific" }); return { embeds: [embed] }; }; diff --git a/src/commands/information/shared/user.js b/src/commands/information/shared/user.js index 11262c444..a31374b42 100644 --- a/src/commands/information/shared/user.js +++ b/src/commands/information/shared/user.js @@ -6,7 +6,10 @@ module.exports = (member) => { if (color === "#000000") color = EMBED_COLORS.BOT_EMBED; const embed = new MessageEmbed() - .setAuthor(`User information for ${member.displayName}`, member.user.displayAvatarURL()) + .setAuthor({ + name: `User information for ${member.displayName}`, + iconURL: member.user.displayAvatarURL(), + }) .setThumbnail(member.user.displayAvatarURL()) .setColor(color) .addField("User Tag", member.user.tag, true) @@ -15,7 +18,7 @@ module.exports = (member) => { .addField("Discord Registered", member.user.createdAt.toUTCString()) .addField(`Roles [${member.roles.cache.size}]`, member.roles.cache.map((r) => r.name).join(", "), false) .addField("Avatar-URL", member.user.displayAvatarURL({ format: "png" })) - .setFooter(`Requested by ${member.user.tag}`) + .setFooter({ text: `Requested by ${member.user.tag}` }) .setTimestamp(Date.now()); return { embeds: [embed] }; diff --git a/src/commands/information/slash-bot.js b/src/commands/information/slash-bot.js index d0b02727e..ab172b655 100644 --- a/src/commands/information/slash-bot.js +++ b/src/commands/information/slash-bot.js @@ -70,7 +70,7 @@ module.exports = class BotCommand extends Command { function botInvite(client) { const embed = new MessageEmbed() - .setAuthor("Invite") + .setAuthor({ name: "Invite" }) .setColor(EMBED_COLORS.BOT_EMBED) .setThumbnail(client.user.displayAvatarURL()) .setDescription("Hey there! Thanks for considering to invite me\nUse the button below to navigate where you want"); diff --git a/src/commands/invites/addinvites.js b/src/commands/invites/addinvites.js index 114bd9749..235155dd0 100644 --- a/src/commands/invites/addinvites.js +++ b/src/commands/invites/addinvites.js @@ -72,7 +72,7 @@ async function addInvites({ guild }, user, amount) { await memberDb.save(); const embed = new MessageEmbed() - .setAuthor(`Added invites to ${user.username}`) + .setAuthor({ name: `Added invites to ${user.username}` }) .setThumbnail(user.displayAvatarURL()) .setColor(EMBED_COLORS.BOT_EMBED) .setDescription(`${user.tag} now has ${getEffectiveInvites(memberDb.invite_data)} invites`); diff --git a/src/commands/invites/invitecodes.js b/src/commands/invites/invitecodes.js index 443250205..175d7ed34 100644 --- a/src/commands/invites/invitecodes.js +++ b/src/commands/invites/invitecodes.js @@ -59,7 +59,7 @@ async function getInviteCodes({ guild }, user) { }); const embed = new MessageEmbed() - .setAuthor(`Invite code for ${user.username}`) + .setAuthor({ name: `Invite code for ${user.username}` }) .setColor(EMBED_COLORS.BOT_EMBED) .setDescription(str); diff --git a/src/commands/invites/inviter.js b/src/commands/invites/inviter.js index ea4027572..2a23682b7 100644 --- a/src/commands/invites/inviter.js +++ b/src/commands/invites/inviter.js @@ -64,7 +64,7 @@ async function getInviter({ guild }, user) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setAuthor(`Invite data for ${user.username}`) + .setAuthor({ name: `Invite data for ${user.username}` }) .setDescription( outdent` Inviter: \`${inviter?.tag || "Deleted User"}\` diff --git a/src/commands/invites/inviteranks.js b/src/commands/invites/inviteranks.js index f83daaf63..277addcc7 100644 --- a/src/commands/invites/inviteranks.js +++ b/src/commands/invites/inviteranks.js @@ -50,6 +50,9 @@ async function getInviteRanks({ guild }) { } }); - const embed = new MessageEmbed().setAuthor("Invite Ranks").setColor(EMBED_COLORS.BOT_EMBED).setDescription(str); + const embed = new MessageEmbed() + .setAuthor({ name: "Invite Ranks" }) + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription(str); return { embeds: [embed] }; } diff --git a/src/commands/invites/invites.js b/src/commands/invites/invites.js index 69f7ffd37..984c99f40 100644 --- a/src/commands/invites/invites.js +++ b/src/commands/invites/invites.js @@ -58,7 +58,7 @@ async function getInvites({ guild }, user) { const inviteData = (await getMember(guild.id, user.id)).invite_data; const embed = new MessageEmbed() - .setAuthor(`Invites for ${user.username}`) + .setAuthor({ name: `Invites for ${user.username}` }) .setColor(EMBED_COLORS.BOT_EMBED) .setThumbnail(user.displayAvatarURL()) .setDescription(`${user.toString()} has ${getEffectiveInvites(inviteData)} invites`) diff --git a/src/commands/invites/invitetracker.js b/src/commands/invites/invitetracker.js index 5cbb816eb..7e4099d66 100644 --- a/src/commands/invites/invitetracker.js +++ b/src/commands/invites/invitetracker.js @@ -81,7 +81,7 @@ async function setStatus({ guild }, input) { await cacheGuildInvites(guild); } else { - this.client.inviteCache.delete(guild.id); + guild.client.inviteCache.delete(guild.id); } const settings = await getSettings(guild); diff --git a/src/commands/moderation/timeout.js b/src/commands/moderation/timeout.js new file mode 100644 index 000000000..6ea19a5d7 --- /dev/null +++ b/src/commands/moderation/timeout.js @@ -0,0 +1,81 @@ +const { Command } = require("@src/structures"); +const { timeoutTarget } = require("@utils/modUtils"); +const { Message, CommandInteraction } = require("discord.js"); +const { resolveMember } = require("@utils/guildUtils"); + +module.exports = class Timeout extends Command { + constructor(client) { + super(client, { + name: "timeout", + description: "timeouts the specified member", + category: "MODERATION", + botPermissions: ["MODERATE_MEMBERS"], + userPermissions: ["MODERATE_MEMBERS"], + command: { + enabled: true, + aliases: ["mute"], + usage: " [reason]", + minArgsCount: 1, + }, + slashCommand: { + enabled: true, + options: [ + { + name: "user", + description: "the target member", + type: "USER", + required: true, + }, + { + name: "minutes", + description: "the time to timeout the member for", + type: "INTEGER", + required: true, + }, + { + name: "reason", + description: "reason for timeout", + type: "STRING", + required: false, + }, + ], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const target = await resolveMember(message, args[0], true); + if (!target) return message.reply(`No user found matching ${args[0]}`); + const minutes = parseInt(args[1]); + if (isNaN(minutes)) return message.reply("Invalid time. Provide time in minutes."); + const reason = args.slice(2).join(" ").trim(); + const response = await timeout(message.member, target, minutes, reason); + await message.reply(response); + } + + /** + * @param {CommandInteraction} interaction + */ + async interactionRun(interaction) { + const user = interaction.options.getUser("user"); + const minutes = interaction.options.getInteger("minutes"); + const reason = interaction.options.getString("reason"); + const target = await interaction.guild.members.fetch(user.id); + + const response = await timeout(interaction.member, target, minutes, reason); + await interaction.followUp(response); + } +}; + +async function timeout(issuer, target, minutes, reason) { + const response = await timeoutTarget(issuer, target, minutes, reason); + if (typeof response === "boolean") return `${target.user.tag} is timed out!`; + if (response === "BOT_PERM") return `I do not have permission to timeout ${target.user.tag}`; + else if (response === "MEMBER_PERM") return `You do not have permission to timeout ${target.user.tag}`; + else if (response === "ALREADY_TIMEOUT") return `${target.user.tag} is already timed out!`; + else return `Failed to timeout ${target.user.tag}`; +} diff --git a/src/commands/moderation/unmute.js b/src/commands/moderation/unmute.js deleted file mode 100644 index 57ea1bc9f..000000000 --- a/src/commands/moderation/unmute.js +++ /dev/null @@ -1,54 +0,0 @@ -const { Command } = require("@src/structures"); -const { Message, CommandInteraction } = require("discord.js"); -const { unmuteTarget } = require("@utils/modUtils"); -const { resolveMember } = require("@utils/guildUtils"); - -module.exports = class UnmuteCommand extends Command { - constructor(client) { - super(client, { - name: "unmute", - description: "umutes the specified member", - botPermissions: ["MANAGE_ROLES"], - userPermissions: ["MUTE_MEMBERS"], - category: "MODERATION", - command: { - enabled: true, - usage: "<@member> [reason]", - minArgsCount: 1, - }, - }); - } - - /** - * @param {Message} message - * @param {string[]} args - */ - async messageRun(message, args) { - const target = await resolveMember(message, args[0], true); - if (!target) return message.reply(`No user found matching ${args[0]}`); - const reason = message.content.split(args[0])[1].trim(); - const response = await unmute(message.member, target, reason); - await message.reply(response); - } - - /** - * @param {CommandInteraction} interaction - */ - async interactionRun(interaction) { - const user = interaction.options.getUser("user"); - const reason = interaction.options.getString("reason"); - const target = await interaction.guild.members.fetch(user.id); - - const response = await unmute(interaction.member, target, reason); - await interaction.followUp(response); - } -}; - -async function unmute(issuer, target, reason) { - const response = await unmuteTarget(issuer, target, reason); - if (typeof response === "boolean") return `${target.user.tag} is unmuted!`; - if (response === "BOT_PERM") return `I do not have permission to unmute ${target.user.tag}`; - else if (response === "MEMBER_PERM") return `You do not have permission to unmute ${target.user.tag}`; - else if (response === "NOT_MUTED") return `${target.user.tag} is not muted in this server`; - else return `Failed to unmute ${target.user.tag}`; -} diff --git a/src/commands/moderation/mute.js b/src/commands/moderation/untimeout.js similarity index 53% rename from src/commands/moderation/mute.js rename to src/commands/moderation/untimeout.js index ee00d4053..613d8b6e2 100644 --- a/src/commands/moderation/mute.js +++ b/src/commands/moderation/untimeout.js @@ -1,19 +1,20 @@ const { Command } = require("@src/structures"); +const { unTimeoutTarget } = require("@utils/modUtils"); const { Message, CommandInteraction } = require("discord.js"); -const { muteTarget } = require("@utils/modUtils"); const { resolveMember } = require("@utils/guildUtils"); -module.exports = class MuteCommand extends Command { +module.exports = class Timeout extends Command { constructor(client) { super(client, { - name: "mute", - description: "mutes the specified member", + name: "untimeout", + description: "remove timeout from a member", category: "MODERATION", - botPermissions: ["MANAGE_ROLES"], - userPermissions: ["MUTE_MEMBERS"], + botPermissions: ["MODERATE_MEMBERS"], + userPermissions: ["MODERATE_MEMBERS"], command: { enabled: true, - usage: "<@member> [reason]", + aliases: ["unmute"], + usage: " [reason]", minArgsCount: 1, }, slashCommand: { @@ -27,7 +28,7 @@ module.exports = class MuteCommand extends Command { }, { name: "reason", - description: "reason for mute", + description: "reason for timeout", type: "STRING", required: false, }, @@ -43,8 +44,8 @@ module.exports = class MuteCommand extends Command { async messageRun(message, args) { const target = await resolveMember(message, args[0], true); if (!target) return message.reply(`No user found matching ${args[0]}`); - const reason = message.content.split(args[0])[1].trim(); - const response = await mute(message.member, target, reason); + const reason = args.slice(1).join(" ").trim(); + const response = await untimeout(message.member, target, reason); await message.reply(response); } @@ -56,20 +57,16 @@ module.exports = class MuteCommand extends Command { const reason = interaction.options.getString("reason"); const target = await interaction.guild.members.fetch(user.id); - const response = await mute(interaction.member, target, reason); + const response = await untimeout(interaction.member, target, reason); await interaction.followUp(response); } }; -async function mute(issuer, target, reason) { - const response = await muteTarget(issuer, target, reason); - if (typeof response === "boolean") return `${target.user.tag} is now muted!`; - if (response === "BOT_PERM") return `I do not have permission to mute ${target.user.tag}`; - else if (response === "MEMBER_PERM") return `You do not have permission to mute ${target.user.tag}`; - else if (response === "ALREADY_MUTED") return `${target.user.tag} is already muted on this server`; - else if (response === "NO_MUTED_ROLE") - return "There is no muted role in this server. Create a `Muted` role or use `mutesetup` to automatically create one"; - else if (response === "NO_MUTED_PERMISSION") - return "I do not have permission to move members to `Muted` role. Is that role below my highest role?"; - else return `Failed to mute ${target.user.tag}`; +async function untimeout(issuer, target, reason) { + const response = await unTimeoutTarget(issuer, target, reason); + if (typeof response === "boolean") return `Timeout of ${target.user.tag} is removed!`; + if (response === "BOT_PERM") return `I do not have permission to remove timeout of ${target.user.tag}`; + else if (response === "MEMBER_PERM") return `You do not have permission to remove timeout of ${target.user.tag}`; + else if (response === "NO_TIMEOUT") return `${target.user.tag} is not timed out!`; + else return `Failed to remove timeout of ${target.user.tag}`; } diff --git a/src/commands/moderation/warnings.js b/src/commands/moderation/warnings.js new file mode 100644 index 000000000..588f6bca5 --- /dev/null +++ b/src/commands/moderation/warnings.js @@ -0,0 +1,146 @@ +const { Command } = require("@src/structures"); +const { Message, CommandInteraction, MessageEmbed } = require("discord.js"); +const { resolveMember } = require("@utils/guildUtils"); +const { getWarningLogs, clearWarningLogs } = require("@schemas/ModLog"); +const { getMember } = require("@schemas/Member"); + +module.exports = class Warnings extends Command { + constructor(client) { + super(client, { + name: "warnings", + description: "list or clear user warnings", + category: "MODERATION", + userPermissions: ["KICK_MEMBERS"], + command: { + enabled: true, + minArgsCount: 1, + subcommands: [ + { + trigger: "list [member]", + description: "list all warnings for a user", + }, + { + trigger: "clear ", + description: "clear all warnings for a user", + }, + ], + }, + slashCommand: { + enabled: true, + options: [ + { + name: "list", + description: "list all warnings for a user", + type: "SUB_COMMAND", + options: [ + { + name: "user", + description: "the target member", + type: "USER", + required: true, + }, + ], + }, + { + name: "clear", + description: "clear all warnings for a user", + type: "SUB_COMMAND", + options: [ + { + name: "user", + description: "the target member", + type: "USER", + required: true, + }, + ], + }, + ], + }, + }); + } + + /** + * @param {Message} message + * @param {string[]} args + */ + async messageRun(message, args) { + const sub = args[0]?.toLowerCase(); + let response = ""; + + if (sub === "list") { + const target = (await resolveMember(message, args[1], true)) || message.member; + if (!target) return message.reply(`No user found matching ${args[1]}`); + response = await listWarnings(target, message); + } + + // + else if (sub === "clear") { + const target = await resolveMember(message, args[1], true); + if (!target) return message.reply(`No user found matching ${args[1]}`); + response = await clearWarnings(target, message); + } + + // else + else { + response = `Invalid subcommand ${sub}`; + } + + await message.reply(response); + } + + /** + * @param {CommandInteraction} interaction + */ + async interactionRun(interaction) { + const sub = interaction.options.getSubcommand(); + let response = ""; + + if (sub === "list") { + const user = interaction.options.getUser("user"); + const target = (await interaction.guild.members.fetch(user.id)) || interaction.member; + response = await listWarnings(target, interaction); + } + + // + else if (sub === "clear") { + const user = interaction.options.getUser("user"); + const target = await interaction.guild.members.fetch(user.id); + response = await clearWarnings(target, interaction); + } + + // else + else { + response = `Invalid subcommand ${sub}`; + } + + await interaction.followUp(response); + } +}; + +async function listWarnings(target, { guildId }) { + if (!target) return "No user provided"; + if (target.user.bot) return "Bots don't have warnings"; + + const warnings = await getWarningLogs(guildId, target.id); + if (!warnings.length) return `${target.user.tag} has no warnings`; + + const acc = warnings.map((warning, i) => `${i + 1}. ${warning.reason} [By ${warning.admin.tag}]`).join("\n"); + const embed = new MessageEmbed({ + author: `${target.user.tag}'s warnings`, + description: acc, + }); + + return { embeds: [embed] }; +} + +async function clearWarnings(target, { guildId }) { + if (!target) return "No user provided"; + if (target.user.bot) return "Bots don't have warnings"; + + const memberDb = await getMember(guildId, target.id); + memberDb.warnings = 0; + await memberDb.save(); + + await clearWarningLogs(guildId, target.id); + return `${target.user.tag}'s warnings have been cleared`; +} diff --git a/src/commands/music/np.js b/src/commands/music/np.js index 5d9cdb1c2..d164d5125 100644 --- a/src/commands/music/np.js +++ b/src/commands/music/np.js @@ -48,8 +48,7 @@ function nowPlaying({ client, guildId }) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(track.displayThumbnail("hqdefault")) - .setAuthor("Now playing") + .setAuthor({ name: "Now playing" }) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) .addField("Added By", track.requester.tag || "NA", true) @@ -62,5 +61,8 @@ function nowPlaying({ client, guildId }) { end, false ); + + if (typeof track.displayThumbnail === "function") embed.setThumbnail(track.displayThumbnail("hqdefault")); + return { embeds: [embed] }; } diff --git a/src/commands/music/play.js b/src/commands/music/play.js index b96be1e20..c6def292b 100644 --- a/src/commands/music/play.js +++ b/src/commands/music/play.js @@ -102,12 +102,12 @@ async function play({ member, guild, channel }, user, query) { } embed - .setThumbnail(track.displayThumbnail("hqdefault")) - .setAuthor("Added Song to queue") + .setAuthor({ name: "Added Song to queue" }) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${track.requester.tag}`); + .setFooter({ text: `Requested By: ${track.requester.tag}` }); + if (typeof track.displayThumbnail === "function") embed.setThumbnail(track.displayThumbnail("hqdefault")); if (player.queue.totalSize > 0) embed.addField("Position in Queue", (player.queue.size - 0).toString(), true); return { embeds: [embed] }; @@ -118,11 +118,11 @@ async function play({ member, guild, channel }, user, query) { } embed - .setAuthor("Added Playlist to queue") + .setAuthor({ name: "Added Playlist to queue" }) .setDescription(res.playlist.name) .addField("Enqueued", `${res.tracks.length} songs`, true) .addField("Playlist duration", "`" + prettyMs(res.playlist.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${res.tracks[0].requester.tag}`); + .setFooter({ text: `Requested By: ${res.tracks[0].requester.tag}` }); return { embeds: [embed] }; @@ -135,11 +135,10 @@ async function play({ member, guild, channel }, user, query) { } embed - .setThumbnail(track.displayThumbnail("hqdefault")) - .setAuthor("Added Song to queue") + .setAuthor({ name: "Added Song to queue" }) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${track.requester.tag}`); + .setFooter({ text: `Requested By: ${track.requester.tag}` }); if (player.queue.totalSize > 0) embed.addField("Position in Queue", (player.queue.size - 0).toString(), true); return { embeds: [embed] }; diff --git a/src/commands/music/queue.js b/src/commands/music/queue.js index b1a4cf8ad..e104a895d 100644 --- a/src/commands/music/queue.js +++ b/src/commands/music/queue.js @@ -52,7 +52,7 @@ function getQueue({ client, guild }, pgNo) { if (!player) return "šŸš« There is no music playing in this guild."; const queue = player.queue; - const embed = new MessageEmbed().setColor(EMBED_COLORS.BOT_EMBED).setAuthor(`Queue for ${guild.name}`); + const embed = new MessageEmbed().setColor(EMBED_COLORS.BOT_EMBED).setAuthor({ name: `Queue for ${guild.name}` }); // change for the amount of tracks per page const multiple = 10; @@ -69,7 +69,7 @@ function getQueue({ client, guild }, pgNo) { const maxPages = Math.ceil(queue.length / multiple); - embed.setFooter(`Page ${page > maxPages ? maxPages : page} of ${maxPages}`); + embed.setFooter({ text: `Page ${page > maxPages ? maxPages : page} of ${maxPages}` }); return { embeds: [embed] }; } diff --git a/src/commands/music/search.js b/src/commands/music/search.js index 91b7e1672..563961753 100644 --- a/src/commands/music/search.js +++ b/src/commands/music/search.js @@ -104,10 +104,10 @@ async function search({ member, guild, channel }, user, query) { embed .setThumbnail(track.displayThumbnail("hqdefault")) - .setAuthor("Added Song to queue") + .setAuthor({ name: "Added Song to queue" }) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${track.requester.tag}`); + .setFooter({ text: `Requested By: ${track.requester.tag}` }); if (player.queue.totalSize > 0) embed.addField("Position in Queue", (player.queue.size - 0).toString(), true); return { embeds: [embed] }; @@ -119,11 +119,11 @@ async function search({ member, guild, channel }, user, query) { } embed - .setAuthor("Added Playlist to queue") + .setAuthor({ name: "Added Playlist to queue" }) .setDescription(res.playlist.name) .addField("Enqueued", `${res.tracks.length} songs`, true) .addField("Playlist duration", "`" + prettyMs(res.playlist.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${res.tracks[0].requester.tag}`); + .setFooter({ text: `Requested By: ${res.tracks[0].requester.tag}` }); return { embeds: [embed] }; @@ -145,7 +145,7 @@ async function search({ member, guild, channel }, user, query) { .addOptions(options) ); - embed.setAuthor("Search Results").setDescription(`Please select the songs you wish to add to queue`); + embed.setAuthor({ name: "Search Results" }).setDescription(`Please select the songs you wish to add to queue`); const sentMsg = await channel.send({ embeds: [embed], @@ -178,10 +178,10 @@ async function search({ member, guild, channel }, user, query) { embed .setThumbnail(track.displayThumbnail("hqdefault")) - .setAuthor("Added Song to queue") + .setAuthor({ name: "Added Song to queue" }) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${track.requester.tag}`); + .setFooter({ text: `Requested By: ${track.requester.tag}` }); if (player.queue.totalSize > 0) embed.addField("Position in Queue", (player.queue.size - 0).toString(), true); @@ -196,7 +196,7 @@ async function search({ member, guild, channel }, user, query) { embed .setDescription(`šŸŽ¶ Added ${toAdd.length} songs to queue`) - .setFooter(`Requested By: ${res.tracks[0].requester.tag}`); + .setFooter({ text: `Requested By: ${res.tracks[0].requester.tag}` }); return sentMsg.edit({ embeds: [embed], components: [] }); }); diff --git a/src/commands/owner/eval.js b/src/commands/owner/eval.js index 877507e95..b68804db6 100644 --- a/src/commands/owner/eval.js +++ b/src/commands/owner/eval.js @@ -34,7 +34,10 @@ module.exports = class Eval extends Command { */ async messageRun(message, args) { const input = args.join(" "); + if (!input) return message.reply("Please provide code to eval"); + if (input.toLowerCase().includes("token")) return message.reply("Don't try to hack me!"); + let response; try { const output = eval(input); @@ -50,6 +53,8 @@ module.exports = class Eval extends Command { */ async interactionRun(interaction) { const input = interaction.options.getString("expression"); + if (input.toLowerCase().includes("token")) return interaction.followUp("Don't try to hack me!"); + let response; try { const output = eval(input); @@ -66,7 +71,7 @@ const buildSuccessResponse = (output) => { if (typeof output !== "string") output = require("util").inspect(output, { depth: 0 }); embed - .setAuthor("šŸ“¤ Output") + .setAuthor({ name: "šŸ“¤ Output" }) .setDescription("```js\n" + (output.length > 4096 ? `${output.substr(0, 4000)}...` : output) + "\n```") .setColor("RANDOM") .setTimestamp(Date.now()); @@ -77,7 +82,7 @@ const buildSuccessResponse = (output) => { const buildErrorResponse = (err) => { const embed = new MessageEmbed(); embed - .setAuthor("šŸ“¤ Error") + .setAuthor({ name: "šŸ“¤ Error" }) .setDescription("```js\n" + (err.length > 4096 ? `${err.substr(0, 4000)}...` : err) + "\n```") .setColor(EMBED_COLORS.ERROR) .setTimestamp(Date.now()); diff --git a/src/commands/social/reputation.js b/src/commands/social/reputation.js index 567524b14..8946409cc 100644 --- a/src/commands/social/reputation.js +++ b/src/commands/social/reputation.js @@ -119,7 +119,7 @@ async function viewReputation(target) { if (!userData) return `${target.tag} has no reputation yet`; const embed = new MessageEmbed() - .setAuthor(`Reputation for ${target.username}`) + .setAuthor({ name: `Reputation for ${target.username}` }) .setColor(EMBED_COLORS.BOT_EMBED) .setThumbnail(target.displayAvatarURL()) .addField("Given", userData.reputation?.given.toString(), true) @@ -154,7 +154,7 @@ async function giveReputation(user, target) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) .setDescription(`${target.toString()} +1 Rep!`) - .setFooter(`By ${user.tag}`) + .setFooter({ text: `By ${user.tag}` }) .setTimestamp(Date.now()); return { embeds: [embed] }; diff --git a/src/commands/utility/bigemoji.js b/src/commands/utility/bigemoji.js index 8b14ba6b6..26a6b53a6 100644 --- a/src/commands/utility/bigemoji.js +++ b/src/commands/utility/bigemoji.js @@ -54,9 +54,9 @@ function getEmoji(user, emoji) { const custom = Util.parseEmoji(emoji); const embed = new MessageEmbed() - .setAuthor("āÆ Big Emoji ā®") + .setAuthor({ name: "āÆ Big Emoji ā®" }) .setColor(EMBED_COLORS.BOT_EMBED) - .setFooter(`Requested by ${user.tag}`); + .setFooter({ text: `Requested by ${user.tag}` }); if (custom.id) { embed.setImage(`https://cdn.discordapp.com/emojis/${custom.id}.${custom.animated ? "gif" : "png"}`); diff --git a/src/commands/utility/covid.js b/src/commands/utility/covid.js index 62963e4eb..437668bce 100644 --- a/src/commands/utility/covid.js +++ b/src/commands/utility/covid.js @@ -72,7 +72,7 @@ async function getCovid(country) { .addField("Critical stage", data?.critical.toString(), true) .addField("Cases per 1 million", data?.casesPerOneMillion.toString(), true) .addField("Deaths per 1 million", data?.deathsPerOneMillion.toString(), true) - .setFooter(`Last updated on ${mg}`); + .setFooter({ text: `Last updated on ${mg}` }); return { embeds: [embed] }; } diff --git a/src/commands/utility/github.js b/src/commands/utility/github.js index 5d71271ba..c1f63254e 100644 --- a/src/commands/utility/github.js +++ b/src/commands/utility/github.js @@ -77,7 +77,11 @@ async function getGithubUser(target, author) { if (website == null) website = "Not Provided"; const embed = new MessageEmbed() - .setAuthor(`GitHub User: ${username}`, avatarUrl, userPageLink) + .setAuthor({ + name: `GitHub User: ${username}`, + url: userPageLink, + iconURL: avatarUrl, + }) .addField( "User Info", outdent`**Real Name**: *${name || "Not Provided"}* @@ -90,7 +94,7 @@ async function getGithubUser(target, author) { .setDescription(`**Bio**:\n${bio || "Not Provided"}`) .setImage(avatarUrl) .setColor(0x6e5494) - .setFooter(`Requested by ${author.tag}`); + .setFooter({ text: `Requested by ${author.tag}` }); return { embeds: [embed] }; } diff --git a/src/commands/utility/help.js b/src/commands/utility/help.js index 8b3afd689..a1ac1c328 100644 --- a/src/commands/utility/help.js +++ b/src/commands/utility/help.js @@ -104,7 +104,7 @@ async function getHelpMenu({ client, guild }) { const value = CommandCategory[key]; const data = { label: value.name, - value: value.name, + value: key, description: `View commands in ${value.name} category`, emoji: value.emoji, }; @@ -193,6 +193,7 @@ const waiter = (msg, userId, prefix) => { collector.on("end", () => { if (cache[`${msg.guildId}|${userId}`]) delete cache[`${msg.guildId}|${userId}`]; + if (!msg.guild || !msg.channel) return; return msg.editable && msg.edit({ components: [] }); }); }; @@ -226,8 +227,8 @@ function getSlashCategoryEmbeds(client, category) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription(collector); return [embed]; @@ -239,8 +240,8 @@ function getSlashCategoryEmbeds(client, category) { if (commands.length === 0) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription("No commands in this category"); return [embed]; @@ -267,10 +268,10 @@ function getSlashCategoryEmbeds(client, category) { arrSplitted.forEach((item, index) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription(item.join("\n")) - .setFooter(`page ${index + 1} of ${arrSplitted.length}`); + .setFooter({ text: `page ${index + 1} of ${arrSplitted.length}` }); arrEmbeds.push(embed); }); @@ -305,8 +306,8 @@ function getMsgCategoryEmbeds(client, category, prefix) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription(collector); return [embed]; @@ -318,8 +319,8 @@ function getMsgCategoryEmbeds(client, category, prefix) { if (commands.length === 0) { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription("No commands in this category"); return [embed]; @@ -337,12 +338,12 @@ function getMsgCategoryEmbeds(client, category, prefix) { arrSplitted.forEach((item, index) => { const embed = new MessageEmbed() .setColor(EMBED_COLORS.BOT_EMBED) - .setThumbnail(CommandCategory[category].image) - .setAuthor(`${category} Commands`) + .setThumbnail(CommandCategory[category]?.image) + .setAuthor({ name: `${category} Commands` }) .setDescription(item.join("\n")) - .setFooter( - `page ${index + 1} of ${arrSplitted.length} | Type ${prefix}help for more command information` - ); + .setFooter({ + text: `page ${index + 1} of ${arrSplitted.length} | Type ${prefix}help for more command information`, + }); arrEmbeds.push(embed); }); diff --git a/src/commands/utility/paste.js b/src/commands/utility/paste.js index e2b17eabd..4227d8d70 100644 --- a/src/commands/utility/paste.js +++ b/src/commands/utility/paste.js @@ -62,7 +62,7 @@ async function paste(content, title) { if (!response) return "āŒ Something went wrong"; const embed = new MessageEmbed() - .setAuthor("Paste links") + .setAuthor({ name: "Paste links" }) .setDescription(`šŸ”ø Normal: ${response.url}\nšŸ”¹ Raw: ${response.raw}`); return { embeds: [embed] }; diff --git a/src/commands/utility/pokedex.js b/src/commands/utility/pokedex.js index 14eb1137d..a6704fc41 100644 --- a/src/commands/utility/pokedex.js +++ b/src/commands/utility/pokedex.js @@ -82,7 +82,7 @@ async function pokedex(pokemon) { ā™¢ **Is Generation?**: ${json.gen} ` ) - .setFooter(json.description); + .setFooter({ text: json.description }); return { embeds: [embed] }; } diff --git a/src/commands/utility/translate.js b/src/commands/utility/translate.js index 8e8247e55..5aea3bfb4 100644 --- a/src/commands/utility/translate.js +++ b/src/commands/utility/translate.js @@ -84,10 +84,13 @@ async function getTranslation(author, input, outputCode) { if (!data) return "Failed to translate your text"; const embed = new MessageEmbed() - .setAuthor(`${author.username} says`, author.avatarURL()) + .setAuthor({ + name: `${author.username} says`, + iconURL: author.avatarURL(), + }) .setColor(EMBED_COLORS.BOT_EMBED) .setDescription(data.output) - .setFooter(`${data.inputLang} (${data.inputCode}) āŸ¶ ${data.outputLang} (${data.outputCode})`); + .setFooter({ text: `${data.inputLang} (${data.inputCode}) āŸ¶ ${data.outputLang} (${data.outputCode})` }); return { embeds: [embed] }; } diff --git a/src/commands/utility/urban.js b/src/commands/utility/urban.js index 5653725a0..79ab1514b 100644 --- a/src/commands/utility/urban.js +++ b/src/commands/utility/urban.js @@ -68,7 +68,7 @@ async function urban(word) { .addField("ID", data.defid.toString(), true) .addField("Likes / Dislikes", `šŸ‘ ${data.thumbs_up} | šŸ‘Ž ${data.thumbs_down}`, true) .addField("Example", data.example, false) - .setFooter(`Created ${moment(data.written_on).fromNow()}`); + .setFooter({ text: `Created ${moment(data.written_on).fromNow()}` }); return { embeds: [embed] }; } diff --git a/src/commands/utility/weather.js b/src/commands/utility/weather.js index 85865afe0..3cafdc6c9 100644 --- a/src/commands/utility/weather.js +++ b/src/commands/utility/weather.js @@ -78,7 +78,7 @@ async function weather(place) { .addField("Humidity", json.current?.humidity.toString() || "NA", true) .addField("Visual distance", `${json.current?.visibility} km`, true) .addField("UV", json.current?.uv_index.toString() || "NA", true) - .setFooter(`Last checked at ${json.current?.observation_time} GMT`); + .setFooter({ text: `Last checked at ${json.current?.observation_time} GMT` }); return { embeds: [embed] }; } diff --git a/src/events/guild/guildCreate.js b/src/events/guild/guildCreate.js index 04be60b3a..4ebcc163d 100644 --- a/src/events/guild/guildCreate.js +++ b/src/events/guild/guildCreate.js @@ -20,7 +20,7 @@ module.exports = async (client, guild) => { .addField("ID", guild.id, false) .addField("Owner", `${client.users.cache.get(guild.ownerId).tag} [\`${guild.ownerId}\`]`, false) .addField("Members", `\`\`\`yaml\n${guild.memberCount}\`\`\``, false) - .setFooter(`Guild #${client.guilds.cache.size}`); + .setFooter({ text: `Guild #${client.guilds.cache.size}` }); client.joinLeaveWebhook.send({ username: "Join", diff --git a/src/events/guild/guildDelete.js b/src/events/guild/guildDelete.js index dc51c57cf..a7e5ff60c 100644 --- a/src/events/guild/guildDelete.js +++ b/src/events/guild/guildDelete.js @@ -23,7 +23,7 @@ module.exports = async (client, guild) => { .addField("ID", guild.id, false) .addField("Owner", `${owner.username}#${owner.discriminator} [\`${owner.id}\`]`, false) .addField("Members", `\`\`\`yaml\n${guild.memberCount}\`\`\``, false) - .setFooter(`Guild #${client.guilds.cache.size}`); + .setFooter({ text: `Guild #${client.guilds.cache.size}` }); client.joinLeaveWebhook.send({ username: "Leave", diff --git a/src/events/member/guildMemberAdd.js b/src/events/member/guildMemberAdd.js index 42e8c0370..75dad2e54 100644 --- a/src/events/member/guildMemberAdd.js +++ b/src/events/member/guildMemberAdd.js @@ -11,8 +11,14 @@ module.exports = async (client, member) => { const { guild } = member; const settings = await getSettings(guild); + // Autorole + if (settings.autorole) { + const role = guild.roles.cache.get(settings.autorole); + if (role) member.roles.add(role).catch((err) => {}); + } + // Check for counter channel - if (settings.counters.find((doc) => ["MEMBERS", "BOTS"].includes(doc.counter_type))) { + if (settings.counters.find((doc) => ["MEMBERS", "BOTS", "USERS"].includes(doc.counter_type.toUpperCase()))) { if (member.user.bot) { settings.data.bots += 1; await settings.save(); diff --git a/src/events/member/guildMemberRemove.js b/src/events/member/guildMemberRemove.js index 2918b9b73..5aa0d3890 100644 --- a/src/events/member/guildMemberRemove.js +++ b/src/events/member/guildMemberRemove.js @@ -13,7 +13,7 @@ module.exports = async (client, member) => { const settings = await getSettings(guild); // Check for counter channel - if (settings.counters.find((doc) => ["MEMBERS", "BOTS"].includes(doc.counter_type))) { + if (settings.counters.find((doc) => ["MEMBERS", "BOTS", "USERS"].includes(doc.counter_type.toUpperCase()))) { if (member.user.bot) { settings.data.bots -= 1; await settings.save(); diff --git a/src/events/message/messageCreate.js b/src/events/message/messageCreate.js index 47beb1edb..c8c6fa898 100644 --- a/src/events/message/messageCreate.js +++ b/src/events/message/messageCreate.js @@ -1,5 +1,6 @@ const { automodHandler, xpHandler } = require("@src/handlers"); const { getSettings } = require("@schemas/Guild"); +const { sendMessage } = require("@utils/botUtils"); /** * @param {import('@src/structures').BotClient} client @@ -11,7 +12,9 @@ module.exports = async (client, message) => { const { prefix } = settings; // check for bot mentions - if (message.content.includes(`${client.user.id}`)) message.reply(`My prefix is \`${settings.prefix}\``); + if (message.content.includes(`${client.user.id}`)) { + sendMessage(message.channel, `My prefix is \`${settings.prefix}\``); + } let isCommand = false; if (message.content.startsWith(prefix)) { diff --git a/src/events/message/messageDelete.js b/src/events/message/messageDelete.js index 1078bd9b6..5454b3848 100644 --- a/src/events/message/messageDelete.js +++ b/src/events/message/messageDelete.js @@ -20,7 +20,7 @@ module.exports = async (client, message) => { if (!logChannel) return; const embed = new MessageEmbed() - .setAuthor("Ghost ping detected") + .setAuthor({ name: "Ghost ping detected" }) .setDescription( `**Message:**\n${message.content}\n\n` + `**Author:** ${message.author.tag} \`${message.author.id}\`\n` + @@ -29,7 +29,7 @@ module.exports = async (client, message) => { .addField("Members", members.size.toString(), true) .addField("Roles", roles.size.toString(), true) .addField("Everyone?", everyone.toString(), true) - .setFooter(`Sent at: ${message.createdAt}`); + .setFooter({ text: `Sent at: ${message.createdAt}` }); sendMessage(logChannel, { embeds: [embed] }); } diff --git a/src/events/music/trackStart.js b/src/events/music/trackStart.js index 804ccce82..7a59a1e18 100644 --- a/src/events/music/trackStart.js +++ b/src/events/music/trackStart.js @@ -13,13 +13,13 @@ module.exports = (client, player, track, payload) => { const channel = client.channels.cache.get(player.textChannel); const embed = new MessageEmbed() - .setAuthor("Now Playing") - .setThumbnail(track.displayThumbnail("hqdefault")) + .setAuthor({ name: "Now Playing" }) .setColor(client.config.EMBED_COLORS.BOT_EMBED) .setDescription(`[${track.title}](${track.uri})`) .addField("Song Duration", "`" + prettyMs(track.duration, { colonNotation: true }) + "`", true) - .setFooter(`Requested By: ${track.requester.tag}`); + .setFooter({ text: `Requested By: ${track.requester.tag}` }); + if (typeof track.displayThumbnail === "function") embed.setThumbnail(track.displayThumbnail("hqdefault")); if (player.queue.totalSize > 0) embed.addField("Position in Queue", (player.queue.size - 0).toString(), true); sendMessage(channel, { embeds: [embed] }); }; diff --git a/src/events/ready.js b/src/events/ready.js index ff62f3951..16fd143c8 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -2,6 +2,7 @@ const { counterHandler, inviteHandler } = require("@src/handlers"); const { cacheReactionRoles } = require("@schemas/Message"); const { getSettings } = require("@schemas/Guild"); const { updateCounterChannels } = require("@src/handlers/counter"); +const { PRESENCE } = require("@root/config"); /** * @param {import('@src/structures').BotClient} client @@ -14,8 +15,10 @@ module.exports = async (client) => { client.musicManager.init(client.user.id); // Update Bot Presence - updatePresence(client); - setInterval(() => updatePresence(client), 10 * 60 * 1000); + if (PRESENCE.ENABLED) { + updatePresence(client); + setInterval(() => updatePresence(client), 10 * 60 * 1000); + } // Register Interactions if (client.config.INTERACTIONS.SLASH || client.config.INTERACTIONS.CONTEXT) { @@ -47,15 +50,23 @@ module.exports = async (client) => { * @param {import('@src/structures').BotClient} client */ const updatePresence = (client) => { - const guilds = client.guilds.cache; - const members = guilds.map((g) => g.memberCount).reduce((partial_sum, a) => partial_sum + a, 0); + let message = PRESENCE.MESSAGE; + + if (message.includes("{servers}")) { + message = message.replaceAll("{servers}", client.guilds.cache.size); + } + + if (message.includes("{members}")) { + const members = client.guilds.cache.map((g) => g.memberCount).reduce((partial_sum, a) => partial_sum + a, 0); + message = message.replaceAll("{members}", members); + } client.user.setPresence({ - status: "online", + status: PRESENCE.STATUS, activities: [ { - name: `${members} members in ${guilds.size} servers`, - type: "WATCHING", + name: message, + type: PRESENCE.TYPE, }, ], }); diff --git a/src/handlers/automod.js b/src/handlers/automod.js index b1a29aa89..3a9b0b57e 100644 --- a/src/handlers/automod.js +++ b/src/handlers/automod.js @@ -121,11 +121,14 @@ async function performAutomod(message, settings) { // send automod log embed - .setAuthor("Auto Moderation") + .setAuthor({ name: "Auto Moderation" }) .setThumbnail(author.displayAvatarURL()) .setColor(EMBED_COLORS.AUTOMOD) .setDescription(`**Channel:** ${channel.toString()}\n**Content:**\n${content}`) - .setFooter(`By ${author.tag} | ${author.id}`, author.avatarURL()); + .setFooter({ + text: `By ${author.tag} | ${author.id}`, + iconURL: author.avatarURL(), + }); sendMessage(logChannel, { embeds: [embed] }); @@ -133,7 +136,7 @@ async function performAutomod(message, settings) { const strikeEmbed = new MessageEmbed() .setColor(EMBED_COLORS.AUTOMOD) .setThumbnail(message.guild.iconURL()) - .setAuthor("Auto Moderation") + .setAuthor({ name: "Auto Moderation" }) .setDescription( `You have received ${strikesTotal} strikes!\n\n` + `**Guild:** ${message.guild.name}\n` + diff --git a/src/handlers/counter.js b/src/handlers/counter.js index 40732e338..230d0407b 100644 --- a/src/handlers/counter.js +++ b/src/handlers/counter.js @@ -23,9 +23,9 @@ async function updateCounterChannels(client) { if (!vc) continue; let channelName; - if (config.counter_type === "USERS") channelName = `${config.name} : ${all}`; - if (config.counter_type === "MEMBERS") channelName = `${config.name} : ${members}`; - if (config.counter_type === "BOTS") channelName = `${config.name} : ${bots}`; + if (config.counter_type.toUpperCase() === "USERS") channelName = `${config.name} : ${all}`; + if (config.counter_type.toUpperCase() === "MEMBERS") channelName = `${config.name} : ${members}`; + if (config.counter_type.toUpperCase() === "BOTS") channelName = `${config.name} : ${bots}`; setVoiceChannelName(vc, channelName); } @@ -45,7 +45,7 @@ async function updateCounterChannels(client) { * @param {Object} settings */ async function init(guild, settings) { - if (settings.counters.find((doc) => ["MEMBERS", "BOTS"].includes(doc.counter_type))) { + if (settings.counters.find((doc) => ["MEMBERS", "BOTS"].includes(doc.counter_type.toUpperCase()))) { const stats = await getMemberStats(guild); settings.data.bots = stats[1]; // update bot count in database await settings.save(); diff --git a/src/handlers/greeting.js b/src/handlers/greeting.js index ef80ed2df..cf496abd9 100644 --- a/src/handlers/greeting.js +++ b/src/handlers/greeting.js @@ -16,9 +16,15 @@ const parse = async (content, member, inviterData = {}) => { if (content.includes("{inviter:")) { const inviterId = inviterData.member_id || "NA"; if (inviterId !== "VANITY" && inviterId !== "NA") { - const inviter = await member.guild.members.fetch(inviterId); - inviteData.name = inviter.displayName; - inviteData.tag = inviter.user.tag; + try { + const inviter = await member.client.users.fetch(inviterId); + inviteData.name = inviter.username; + inviteData.tag = inviter.tag; + } catch (ex) { + member.client.logger.error(`Parsing inviterId: ${inviterId}`, ex); + inviteData.name = "NA"; + inviteData.tag = "NA"; + } } else { inviteData.name = inviterId; inviteData.tag = inviterId; @@ -54,7 +60,7 @@ const buildGreeting = async (member, type, config, inviterData) => { if (config.embed.color) embed.setColor(config.embed.color); if (config.embed.thumbnail) embed.setThumbnail(member.user.displayAvatarURL()); if (config.embed.footer) { - embed.setFooter(await parse(config.embed.footer, member, inviterData)); + embed.setFooter({ text: await parse(config.embed.footer, member, inviterData) }); } // set default message diff --git a/src/handlers/reaction.js b/src/handlers/reaction.js index 86c98edec..dda95508f 100644 --- a/src/handlers/reaction.js +++ b/src/handlers/reaction.js @@ -88,9 +88,12 @@ async function handleFlagReaction(countryCode, message, user) { const embed = new MessageEmbed() .setColor(message.client.config.EMBED_COLORS.BOT_EMBED) - .setAuthor(`Translation from ${src}`) + .setAuthor({ name: `Translation from ${src}` }) .setDescription(desc) - .setFooter(`Requested by ${user.tag}`, user.displayAvatarURL()); + .setFooter({ + text: `Requested by ${user.tag}`, + iconURL: user.displayAvatarURL(), + }); sendMessage(message.channel, { embeds: [embed], components: [btnRow] }).then( () => user.client.flagTranslateCache.set(user.id, Date.now()) // set cooldown diff --git a/src/handlers/ticket.js b/src/handlers/ticket.js index 9f4d722de..33030245c 100644 --- a/src/handlers/ticket.js +++ b/src/handlers/ticket.js @@ -1,11 +1,11 @@ -const { getConfig } = require("@schemas/Message"); +const { getTicketConfig } = require("@schemas/Message"); const { closeTicket, openTicket } = require("@utils/ticketUtils"); /** * @param {import("discord.js").ButtonInteraction} interaction */ async function handleTicketOpen(interaction) { - const config = await getConfig(interaction.guildId, interaction.channelId, interaction.message.id); + const config = await getTicketConfig(interaction.guildId, interaction.channelId, interaction.message.id); if (!config) return; const status = await openTicket(interaction.guild, interaction.user, config.ticket); diff --git a/src/helpers/logger.js b/src/helpers/logger.js index 4cb017531..a870f8ce9 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -20,7 +20,7 @@ const sendWebhook = (content, err) => { const embed = new MessageEmbed() .setColor(config.EMBED_COLORS.ERROR) - .setAuthor(err?.name || "Error") + .setAuthor({ name: err?.name || "Error" }) .setDescription("```js\n" + (errString.length > 4096 ? `${errString.substr(0, 4000)}...` : errString) + "\n```"); if (err?.description) embed.addField("Description", content); diff --git a/src/schemas/Giveaways.js b/src/schemas/Giveaways.js new file mode 100644 index 000000000..cbfa1c81b --- /dev/null +++ b/src/schemas/Giveaways.js @@ -0,0 +1,61 @@ +const mongoose = require("mongoose"); + +const Schema = new mongoose.Schema( + { + messageId: String, + channelId: String, + guildId: String, + startAt: Number, + endAt: Number, + ended: Boolean, + winnerCount: Number, + prize: String, + messages: { + giveaway: String, + giveawayEnded: String, + inviteToParticipate: String, + drawing: String, + dropMessage: String, + winMessage: mongoose.Mixed, + embedFooter: mongoose.Mixed, + noWinner: String, + winners: String, + endedAt: String, + hostedBy: String, + }, + thumbnail: String, + hostedBy: String, + winnerIds: { type: [String], default: undefined }, + reaction: mongoose.Mixed, + botsCanWin: Boolean, + embedColor: mongoose.Mixed, + embedColorEnd: mongoose.Mixed, + exemptPermissions: { type: [], default: undefined }, + exemptMembers: String, + bonusEntries: String, + extraData: mongoose.Mixed, + lastChance: { + enabled: Boolean, + content: String, + threshold: Number, + embedColor: mongoose.Mixed, + }, + pauseOptions: { + isPaused: Boolean, + content: String, + unPauseAfter: Number, + embedColor: mongoose.Mixed, + durationAfterPause: Number, + }, + isDrop: Boolean, + allowedMentions: { + parse: { type: [String], default: undefined }, + users: { type: [String], default: undefined }, + roles: { type: [String], default: undefined }, + }, + }, + { id: false } +); + +const Model = mongoose.model("giveaways", Schema); +module.exports = Model; diff --git a/src/schemas/Guild.js b/src/schemas/Guild.js index c48a6ccbb..88645a4fa 100644 --- a/src/schemas/Guild.js +++ b/src/schemas/Guild.js @@ -114,6 +114,7 @@ const Schema = mongoose.Schema({ footer: String, }, }, + autorole: String, }); const Model = mongoose.model("guild", Schema); @@ -137,6 +138,10 @@ module.exports = { }, }); + if (!guild.id) { + throw new Error("Guild ID is undefined"); + } + await guildData.save(); } cache.add(guild.id, guildData); diff --git a/src/schemas/Member.js b/src/schemas/Member.js index 403f5f298..6e14cbbee 100644 --- a/src/schemas/Member.js +++ b/src/schemas/Member.js @@ -60,11 +60,30 @@ module.exports = { return member; }, - getTop100: async (guildId) => + getXpLb: async (guildId, limit = 10) => Model.find({ guild_id: guildId, }) - .limit(100) + .limit(limit) .sort({ level: -1, xp: -1 }) .lean(), + + getInvitesLb: async (guildId, limit = 10) => + Model.aggregate([ + { $match: { guild_id: guildId } }, + { + $project: { + member_id: "$member_id", + invites: { + $subtract: [ + { $add: ["$invite_data.tracked", "$invite_data.added"] }, + { $add: ["$invite_data.left", "$invite_data.fake"] }, + ], + }, + }, + }, + { $match: { invites: { $gt: 0 } } }, + { $sort: { invites: -1 } }, + { $limit: limit }, + ]), }; diff --git a/src/schemas/Message.js b/src/schemas/Message.js index 5f2d7fb1d..ecdca1d09 100644 --- a/src/schemas/Message.js +++ b/src/schemas/Message.js @@ -25,16 +25,17 @@ const Schema = mongoose.Schema({ const Model = mongoose.model("messages", Schema); // Cache -const cache = new Map(); +const rrCache = new Map(); const getKey = (guildId, channelId, messageId) => `${guildId}|${channelId}|${messageId}`; module.exports = { - getConfig: async (guildId, channelId, messageId) => + getTicketConfig: async (guildId, channelId, messageId) => Model.findOne({ guild_id: guildId, channel_id: channelId, message_id: messageId, - }).lean({ defaults: true }), + ticket: { $exists: true }, + }).lean(), createNewTicket: async (guildId, channelId, messageId, title, roleId) => new Model({ @@ -49,10 +50,10 @@ module.exports = { cacheReactionRoles: async (client) => { // clear previous cache - cache.clear(); + rrCache.clear(); // load all docs from database - const docs = await Model.find().lean({ defaults: true }); + const docs = await Model.find({ roles: { $exists: true, $ne: [] } }).lean(); // validate and cache docs for (const doc of docs) { @@ -66,11 +67,11 @@ module.exports = { continue; } const key = getKey(doc.guild_id, doc.channel_id, doc.message_id); - cache.set(key, doc.roles); + rrCache.set(key, doc.roles); } }, - getReactionRoles: (guildId, channelId, messageId) => cache.get(getKey(guildId, channelId, messageId)) || [], + getReactionRoles: (guildId, channelId, messageId) => rrCache.get(getKey(guildId, channelId, messageId)) || [], addReactionRole: async (guildId, channelId, messageId, emote, roleId) => { const filter = { guild_id: guildId, channel_id: channelId, message_id: messageId }; @@ -86,11 +87,11 @@ module.exports = { }, }, { upsert: true, new: true } - ).lean({ defaults: true }); + ).lean(); // update cache const key = getKey(guildId, channelId, messageId); - cache.set(key, data.roles); + rrCache.set(key, data.roles); }, removeReactionRole: async (guildId, channelId, messageId) => { @@ -99,6 +100,6 @@ module.exports = { channel_id: channelId, message_id: messageId, }); - cache.delete(getKey(guildId, channelId, messageId)); + rrCache.delete(getKey(guildId, channelId, messageId)); }, }; diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index c5635feee..a7070bd9d 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -3,11 +3,11 @@ const path = require("path"); const fs = require("fs"); const { table } = require("table"); const mongoose = require("mongoose"); -mongoose.plugin(require("mongoose-lean-defaults").default); const logger = require("../helpers/logger"); const MusicManager = require("./MusicManager"); const Command = require("./Command"); const BaseContext = require("./BaseContext"); +const GiveawayManager = require("./GiveawayManager"); module.exports = class BotClient extends Client { constructor() { @@ -63,6 +63,9 @@ module.exports = class BotClient extends Client { // Music Player this.musicManager = new MusicManager(this); + // Giveaways + this.giveawaysManager = new GiveawayManager(this); + // Logger this.logger = logger; } @@ -96,8 +99,11 @@ module.exports = class BotClient extends Client { if (stat.isDirectory()) { readCommands(path.join(dir, file)); } else { - const extension = file.split(".").at(-1); - if (extension !== "js") return; + const extension = path.extname(file); + if (extension !== ".js") { + this.logger.debug(`getAbsoluteFilePaths - Skipping ${file}: not a js file`); + return; + } const filePath = path.join(__appRoot, dir, file); filePaths.push(filePath); } @@ -119,10 +125,10 @@ module.exports = class BotClient extends Client { const musicEvents = []; this.getAbsoluteFilePaths(directory).forEach((filePath) => { - const file = filePath.replace(/^.*[\\/]/, ""); + const file = path.basename(filePath); const dirName = path.basename(path.dirname(filePath)); try { - const eventName = file.split(".")[0]; + const eventName = path.basename(file, ".js"); const event = require(filePath); // music events @@ -217,7 +223,7 @@ module.exports = class BotClient extends Client { loadCommands(directory) { this.logger.log(`Loading commands...`); this.getAbsoluteFilePaths(directory).forEach((filePath) => { - const file = filePath.replace(/^.*[\\/]/, ""); + const file = path.basename(filePath); try { const cmdClass = require(filePath); if (!(cmdClass.prototype instanceof Command)) return; @@ -239,7 +245,7 @@ module.exports = class BotClient extends Client { loadContexts(directory) { this.logger.log(`Loading contexts...`); this.getAbsoluteFilePaths(directory).forEach((filePath) => { - const file = filePath.replace(/^.*[\\/]/, ""); + const file = path.basename(filePath); try { const ctxClass = require(filePath); if (!(ctxClass.prototype instanceof BaseContext)) return; diff --git a/src/structures/Command.js b/src/structures/Command.js index 76d321334..216d484f6 100644 --- a/src/structures/Command.js +++ b/src/structures/Command.js @@ -113,6 +113,8 @@ class Command { * @param {string} prefix */ async executeCommand(message, args, invoke, prefix) { + if (!message.channel.permissionsFor(message.guild.me).has("SEND_MESSAGES")) return; + // callback validations for (const validation of this.validations) { if (!validation.callback(message)) { @@ -133,7 +135,6 @@ class Command { } // bot permissions - if (!message.channel.permissionsFor(message.guild.me).has("SEND_MESSAGES")) return; if (this.botPermissions.length > 0) { if (!message.channel.permissionsFor(message.guild.me).has(this.botPermissions)) { return message.reply(`I need ${parsePermissions(this.botPermissions)} for this command`); @@ -252,7 +253,7 @@ class Command { } const embed = new MessageEmbed().setColor(EMBED_COLORS.BOT_EMBED).setDescription(desc); - if (title) embed.setAuthor(title); + if (title) embed.setAuthor({ name: title }); return embed; } @@ -273,10 +274,10 @@ class Command { if (this.slashCommand.options.find((o) => o.type === "SUB_COMMAND")) { const subCmds = this.slashCommand.options.filter((opt) => opt.type === "SUB_COMMAND"); subCmds.forEach((sub) => { - desc += `āÆ \`${this.name} ${sub.name}\`: ${sub.description}\n`; + desc += `\`/${this.name} ${sub.name}\`\nāÆ ${sub.description}\n\n`; }); } else { - desc += `\`/${this.name}\`\n${this.description}\n`; + desc += `\`/${this.name}\`\n\n**Help:** ${this.description}`; } if (this.cooldown) { diff --git a/src/structures/CommandCategory.js b/src/structures/CommandCategory.js index aa125b837..08afb019e 100644 --- a/src/structures/CommandCategory.js +++ b/src/structures/CommandCategory.js @@ -24,6 +24,11 @@ module.exports = { image: "https://icons.iconarchive.com/icons/flameia/aqua-smiles/128/make-fun-icon.png", emoji: "šŸ˜‚", }, + GIVEAWAY: { + name: "Giveaway", + image: "https://icons.iconarchive.com/icons/flameia/aqua-smiles/128/make-fun-icon.png", + emoji: "šŸŽ‰", + }, IMAGE: { name: "Image", image: "https://icons.iconarchive.com/icons/dapino/summer-holiday/128/photo-icon.png", diff --git a/src/structures/GiveawayManager.js b/src/structures/GiveawayManager.js new file mode 100644 index 000000000..5574be828 --- /dev/null +++ b/src/structures/GiveawayManager.js @@ -0,0 +1,36 @@ +const { GiveawaysManager } = require("discord-giveaways"); +const Model = require("@schemas/Giveaways"); +const { EMBED_COLORS } = require("@root/config"); + +// Explanation at: https://github.com/Androz2091/discord-giveaways/blob/master/examples/custom-databases/mongoose.js +module.exports = class extends GiveawaysManager { + constructor(client) { + super(client, { + default: { + botsCanWin: false, + embedColor: EMBED_COLORS.GIVEAWAYS, + embedColorEnd: EMBED_COLORS.GIVEAWAYS, + reaction: "šŸŽ", + }, + }); + } + + async getAllGiveaways() { + return await Model.find().lean().exec(); + } + + async saveGiveaway(messageId, giveawayData) { + await Model.create(giveawayData); + return true; + } + + async editGiveaway(messageId, giveawayData) { + await Model.updateOne({ messageId }, giveawayData, { omitUndefined: true }).exec(); + return true; + } + + async deleteGiveaway(messageId) { + await Model.deleteOne({ messageId }).exec(); + return true; + } +}; diff --git a/src/utils/botUtils.js b/src/utils/botUtils.js index 6da91656f..54dc4b12d 100644 --- a/src/utils/botUtils.js +++ b/src/utils/botUtils.js @@ -138,6 +138,9 @@ const permissions = { USE_PUBLIC_THREADS: "Use Public Threads", USE_PRIVATE_THREADS: "Use Private Threads", USE_EXTERNAL_STICKERS: "Use External Stickers", + SEND_MESSAGES_IN_THREADS: "Send Messages In Threads", + START_EMBEDDED_ACTIVITIES: "Start Embedded Activities", + MODERATE_MEMBERS: "Moderate Members", }; /** diff --git a/src/utils/guildUtils.js b/src/utils/guildUtils.js index 47961fcb6..3d3ff2a43 100644 --- a/src/utils/guildUtils.js +++ b/src/utils/guildUtils.js @@ -8,6 +8,7 @@ const CHANNEL_MENTION = /?/; * @param {Guild} guild * @param {string} name */ +// eslint-disable-next-line no-unused-vars function getRoleByName(guild, name) { return guild.roles.cache.find((role) => role.name.toLowerCase() === name); } @@ -77,7 +78,7 @@ async function getMemberStats(guild) { * @return {Role[]} */ function findMatchingRoles(guild, query) { - if (!guild || !query || typeof query !== "string") return; + if (!guild || !query || typeof query !== "string") return []; const patternMatch = query.match(ROLE_MENTION); if (patternMatch) { @@ -162,7 +163,6 @@ async function resolveMembers(message) { } module.exports = { - getRoleByName, canSendEmbeds, getMatchingChannel, setVoiceChannelName, diff --git a/src/utils/modUtils.js b/src/utils/modUtils.js index 80b488e4d..fc701d994 100644 --- a/src/utils/modUtils.js +++ b/src/utils/modUtils.js @@ -4,7 +4,6 @@ const { EMBED_COLORS } = require("@root/config"); // Utils const { sendMessage } = require("@utils/botUtils"); const { containsLink } = require("@utils/miscUtils"); -const { getRoleByName } = require("./guildUtils"); const { error } = require("../helpers/logger"); // Schemas @@ -12,6 +11,8 @@ const { getSettings } = require("@schemas/Guild"); const { getMember } = require("@schemas/Member"); const { addModLogToDb } = require("@schemas/ModLog"); +const DEFAULT_TIMEOUT_DAYS = 7; + /** * @param {import('discord.js').GuildMember} issuer * @param {import('discord.js').GuildMember} target @@ -32,7 +33,7 @@ function memberInteract(issuer, target) { async function addModAction(issuer, target, reason, action) { switch (action) { case "MUTE": - return muteTarget(issuer, target, reason); + return timeoutTarget(issuer, target, DEFAULT_TIMEOUT_DAYS * 24 * 60, reason); case "KICK": return kickTarget(issuer, target, reason); @@ -65,19 +66,19 @@ async function logModeration(issuer, target, reason, type, data = {}) { switch (type.toUpperCase()) { case "PURGE": embed - .setAuthor("Moderation Case - " + type) + .setAuthor({ name: `Moderation Case - ${type}` }) .addField("Issuer", `${issuer.displayName} [${issuer.id}]`, false) .addField("Purge Type", data.purgeType, true) .addField("Messages", data.deletedCount.toString(), true) .addField("Channel", `#${data.channel.name} [${data.channel.id}]`, false); break; - case "MUTE": - embed.setColor(EMBED_COLORS.MUTE_LOG); + case "TIMEOUT": + embed.setColor(EMBED_COLORS.TIMEOUT_LOG); break; - case "UNMUTE": - embed.setColor(EMBED_COLORS.UNMUTE_LOG); + case "UNTIMEOUT": + embed.setColor(EMBED_COLORS.UNTIMEOUT_LOG); break; case "KICK": @@ -119,14 +120,16 @@ async function logModeration(issuer, target, reason, type, data = {}) { if (type.toUpperCase() !== "PURGE") { embed - .setAuthor("Moderation Case - " + type) + .setAuthor({ name: `Moderation Case - ${type}` }) .setThumbnail(target.user.displayAvatarURL()) .addField("Issuer", `${issuer.displayName} [${issuer.id}]`, false) .addField("Member", `${target.displayName} [${target.id}]`, false) .addField("Reason", reason || "No reason provided", true) .setTimestamp(Date.now()); - if (type.toUpperCase() === "MUTE") embed.addField("IsPermanent", "āœ“", true); + if (type.toUpperCase() === "TIMEOUT") { + embed.addField("Expires", ``, true); + } if (type.toUpperCase() === "MOVE") embed.addField("Moved to", data.channel.name, true); } @@ -134,46 +137,6 @@ async function logModeration(issuer, target, reason, type, data = {}) { sendMessage(logChannel, { embeds: [embed] }); } -/** - * Setup muted role - * @param {import('discord.js').Guild} guild - */ -async function setupMutedRole(guild) { - let mutedRole; - - try { - mutedRole = await guild.roles.create({ - name: "Muted", - permissions: [], - color: 11, - position: guild.me.roles.highest.position, - }); - - guild.channels.cache.forEach(async (channel) => { - if (channel.type !== "GUILD_VOICE" && channel.type !== "GUILD_STAGE_VOICE") { - if (channel.permissionsFor(guild.me).has(["VIEW_CHANNEL", "MANAGE_CHANNELS"], true)) { - await channel.permissionOverwrites.create(mutedRole, { - SEND_MESSAGES: false, - ADD_REACTIONS: false, - }); - } - } - - if (channel.type === "GUILD_VOICE" || channel.type === "GUILD_STAGE_VOICE") { - if (channel.permissionsFor(guild.me).has(["VIEW_CHANNEL", "MANAGE_CHANNELS"], true)) { - await channel.permissionOverwrites.create(mutedRole, { - CONNECT: false, - SPEAK: false, - }); - } - } - }); - } catch (ex) { - error("setupMutedRole", ex); - } - return mutedRole; -} - /** * Delete the specified number of messages matching the type * @param {import('discord.js').import('discord.js').GuildMember} issuer @@ -271,68 +234,45 @@ async function warnTarget(issuer, target, reason) { } /** - * Checks if the target has the muted role - * @param {import('discord.js').GuildMember} target - */ -function hasMutedRole(target) { - let mutedRole = getRoleByName(target.guild, "muted"); - return target.roles.cache.has(mutedRole.id); -} - -/** - * Mutes the target and logs to the database, channel + * Timeouts(aka mutes) the target and logs to the database, channel * @param {import('discord.js').GuildMember} issuer * @param {import('discord.js').GuildMember} target + * @param {number} minutes * @param {string} reason */ -async function muteTarget(issuer, target, reason) { +async function timeoutTarget(issuer, target, minutes, reason) { if (!memberInteract(issuer, target)) return "MEMBER_PERM"; if (!memberInteract(issuer.guild.me, target)) return "BOT_PERM"; - - let mutedRole = getRoleByName(issuer.guild, "muted"); - - if (!mutedRole) return "NO_MUTED_ROLE"; - if (!mutedRole.editable) return "NO_MUTED_PERMISSION"; - - const memberDb = await getMember(issuer.guild.id, target.id); - if (memberDb.mute?.active && hasMutedRole(target)) return "ALREADY_MUTED"; + if (target.communicationDisabledUntilTimestamp - Date.now() > 0) return "ALREADY_TIMEOUT"; try { - if (!hasMutedRole(target)) await target.roles.add(mutedRole); - memberDb.mute.active = true; - await memberDb.save(); - logModeration(issuer, target, reason, "Mute", { isPermanent: true }); - + await target.timeout(minutes * 60 * 1000, reason); + logModeration(issuer, target, reason, "Timeout", { minutes }); return true; } catch (ex) { - error("muteTarget", ex); + error("timeoutTarget", ex); return "ERROR"; } } /** - * Unmutes the target and logs to the database, channel + * UnTimeouts(aka mutes) the target and logs to the database, channel * @param {import('discord.js').GuildMember} issuer * @param {import('discord.js').GuildMember} target + * @param {number} minutes * @param {string} reason */ -async function unmuteTarget(issuer, target, reason) { +async function unTimeoutTarget(issuer, target, reason) { if (!memberInteract(issuer, target)) return "MEMBER_PERM"; if (!memberInteract(issuer.guild.me, target)) return "BOT_PERM"; + if (target.communicationDisabledUntilTimestamp - Date.now() < 0) return "NO_TIMEOUT"; - const memberDb = await getMember(issuer.guild.id, target.id); - if (!memberDb.mute?.active && !hasMutedRole(target)) return "NOT_MUTED"; - - let mutedRole = getRoleByName(issuer.guild, "muted"); try { - if (hasMutedRole(target)) await target.roles.remove(mutedRole); - memberDb.mute.active = false; - await memberDb.save(); - - logModeration(issuer, target, reason, "Unmute"); + await target.timeout(0, reason); + logModeration(issuer, target, reason, "UnTimeout"); return true; } catch (ex) { - error("unmuteTarget", ex); + error("unTimeoutTarget", ex); return "ERROR"; } } @@ -543,9 +483,8 @@ module.exports = { addModAction, warnTarget, purgeMessages, - setupMutedRole, - muteTarget, - unmuteTarget, + timeoutTarget, + unTimeoutTarget, kickTarget, softbanTarget, banTarget, diff --git a/src/utils/ticketUtils.js b/src/utils/ticketUtils.js index 1bc4fb53e..9a198a32b 100644 --- a/src/utils/ticketUtils.js +++ b/src/utils/ticketUtils.js @@ -85,7 +85,7 @@ async function closeTicket(channel, closedBy, reason) { if (channel.deletable) await channel.delete(); - const embed = new MessageEmbed().setAuthor("Ticket Closed").setColor(EMBED_COLORS.TICKET_CLOSE); + const embed = new MessageEmbed().setAuthor({ name: "Ticket Closed" }).setColor(EMBED_COLORS.TICKET_CLOSE); if (reason) embed.addField("Reason", reason, false); embed .setDescription(`**Title:** ${ticketDetails.title}`) @@ -179,11 +179,11 @@ async function openTicket(guild, user, config) { }); const embed = new MessageEmbed() - .setAuthor(`Ticket #${ticketNumber}`) + .setAuthor({ name: `Ticket #${ticketNumber}` }) .setDescription( `Hello ${user.toString()}\nSupport will be with you shortly\n\n**Ticket Reason:**\n${config.title}` ) - .setFooter("You may close your ticket anytime by clicking the button below"); + .setFooter({ text: "You may close your ticket anytime by clicking the button below" }); let buttonsRow = new MessageActionRow().addComponents( new MessageButton().setLabel("Close Ticket").setCustomId("TICKET_CLOSE").setEmoji("šŸ”’").setStyle("PRIMARY") @@ -193,7 +193,7 @@ async function openTicket(guild, user, config) { const dmEmbed = new MessageEmbed() .setColor(EMBED_COLORS.TICKET_CREATE) - .setAuthor("Ticket Created") + .setAuthor({ name: "Ticket Created" }) .setThumbnail(guild.iconURL()) .setDescription(`**Server:** ${guild.name}\n**Title:** ${config.title}`);