From 3b213777b431baa767d04a651ba4dce321a97b0f Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Fri, 25 Nov 2022 00:25:13 -0600 Subject: [PATCH 01/26] embed-overhaul Overhaulled all embeds within the bot, added mod logs, and first implantation of buttons. --- src/bot.js | 2 + src/check-nsfw.js | 5 +- src/commands/ban.js | 94 ++++++++------------- src/commands/kick.js | 108 +++++++++++-------------- src/commands/settings.js | 1 + src/commands/warn.js | 117 +++++++++++---------------- src/events/guildMemberAdded.js | 46 +++++++++++ src/events/guildMemberRemove.js | 42 ++++++---- src/events/guildMemberUpdate.js | 38 +++++---- src/events/messageDelete.js | 30 +++---- src/events/messageUpdate.js | 33 +++++--- src/images/events/event-delete.png | Bin 0 -> 3372 bytes src/images/events/event-join.png | Bin 0 -> 10729 bytes src/images/events/event-leave.png | Bin 0 -> 6027 bytes src/images/events/event-nick.png | Bin 0 -> 5335 bytes src/images/events/event-timedout.png | Bin 0 -> 10355 bytes src/images/events/event-update.png | Bin 0 -> 3653 bytes src/images/mod/mod-ban.png | Bin 0 -> 9032 bytes src/images/mod/mod-kick.png | Bin 0 -> 7076 bytes src/images/mod/mod-warn.png | Bin 0 -> 3103 bytes src/util.js | 11 ++- 21 files changed, 275 insertions(+), 252 deletions(-) create mode 100644 src/events/guildMemberAdded.js create mode 100644 src/images/events/event-delete.png create mode 100644 src/images/events/event-join.png create mode 100644 src/images/events/event-leave.png create mode 100644 src/images/events/event-nick.png create mode 100644 src/images/events/event-timedout.png create mode 100644 src/images/events/event-update.png create mode 100644 src/images/mod/mod-ban.png create mode 100644 src/images/mod/mod-kick.png create mode 100644 src/images/mod/mod-warn.png diff --git a/src/bot.js b/src/bot.js index 5dda026..6dd5217 100644 --- a/src/bot.js +++ b/src/bot.js @@ -1,6 +1,7 @@ const Discord = require('discord.js'); const guildMemberRemoveHandler = require('./events/guildMemberRemove'); const guildMemberUpdateHandler = require('./events/guildMemberUpdate'); +const guildMemberAddedHandler = require('./events/guildMemberAdded'); const interactionCreateHandler = require('./events/interactionCreate'); const messageCreateHandler = require('./events/messageCreate'); const messageDeleteHandler = require('./events/messageDelete'); @@ -21,6 +22,7 @@ client.commands = new Discord.Collection(); client.on('ready', readyHandler); client.on('guildMemberRemove', guildMemberRemoveHandler); client.on('guildMemberUpdate', guildMemberUpdateHandler); +client.on('guildMemberAdd', guildMemberAddedHandler) client.on('interactionCreate', interactionCreateHandler); client.on('messageCreate', messageCreateHandler); client.on('messageDelete', messageDeleteHandler); diff --git a/src/check-nsfw.js b/src/check-nsfw.js index ac4d75d..901b96b 100644 --- a/src/check-nsfw.js +++ b/src/check-nsfw.js @@ -150,8 +150,9 @@ async function punishUserNSFW(message, suspectedUrls, suspectedFiles, prediction console.log('Missing NSFW log channel!'); } else { const embed = new Discord.MessageEmbed(); - embed.setTitle(`Suspected NSFW Material sent by ${message.author.tag}`); - embed.setColor(0xffa500); + embed.setTitle('_NSFW Blocked_'); + embed.setDescription(`Suspected NSFW Material sent by ${message.author.tag}`); + embed.setColor(0xdf005d); embed.addFields([ { name: 'Suspected URLs', diff --git a/src/commands/ban.js b/src/commands/ban.js index 9c529fe..c9db5ab 100644 --- a/src/commands/ban.js +++ b/src/commands/ban.js @@ -21,8 +21,10 @@ async function banHandler(interaction) { const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; const bansListEmbed = new Discord.MessageEmbed(); - bansListEmbed.setTitle('User Bans :thumbsdown:'); - bansListEmbed.setColor(0xFFA500); + bansListEmbed.setTitle('User Bans'); + bansListEmbed.setColor(0xa30000); + const image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); + bansListEmbed.setThumbnail('attachment://mod-ban.png'); for (const userId of userIds) { const member = await interaction.guild.members.fetch(userId); @@ -30,10 +32,14 @@ async function banHandler(interaction) { const eventLogEmbed = new Discord.MessageEmbed(); - eventLogEmbed.setColor(0xF24E43); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xa30000); + eventLogEmbed.setDescription(`${user.username} has been banned from Pretendo by ${executor.username}`); eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setTitle('Event Type: _Member Banned_'); + eventLogEmbed.setTitle('_Member Banned_'); eventLogEmbed.setFields( { name: 'User', @@ -44,11 +50,11 @@ async function banHandler(interaction) { value: user.id }, { - name: 'Executor', + name: 'Moderator', value: `<@${executor.id}>` }, { - name: 'Executor User ID', + name: 'Moderator User ID', value: executor.id }, { @@ -56,7 +62,7 @@ async function banHandler(interaction) { value: reason }, { - name: 'From bot /ban command', + name: 'From Bot', value: 'true' } ); @@ -64,27 +70,23 @@ async function banHandler(interaction) { text: 'Pretendo Network', iconURL: guild.iconURL() }); + eventLogEmbed.setThumbnail('attachment://mod-ban.png'); - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); - const { count, rows } = await Bans.findAndCountAll({ + const { count } = await Bans.findAndCountAll({ where: { user_id: member.id } }); - - const sendMemberEmbeds = []; - + const banEmbed = new Discord.MessageEmbed(); - banEmbed.setTitle('Punishment Details'); + banEmbed.setTitle('_Member Banned_'); banEmbed.setDescription('You have been banned from the Pretendo Network server. You may not rejoin at this time, and an appeal may not be possible\nYou may review the details of your ban below'); - banEmbed.setColor(0xF24E43); + banEmbed.setThumbnail('attachment://mod-ban.png'); + banEmbed.setColor(0xa30000); banEmbed.setTimestamp(Date.now()); - banEmbed.setAuthor({ - name: `Banned by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); banEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -92,48 +94,15 @@ async function banHandler(interaction) { banEmbed.setFields({ name: 'Ban Reason', value: reason + }, + { + name: 'Amount Of Times Banned', + value: (count + 1).toString() }); - sendMemberEmbeds.push(banEmbed); - - if (count > 0) { - const pastBansEmbed = new Discord.MessageEmbed(); - pastBansEmbed.setTitle('Past Bans'); - pastBansEmbed.setDescription('For clarifty purposes here is a list of your past bans'); - pastBansEmbed.setColor(0xEF7F31); - pastBansEmbed.setTimestamp(Date.now()); - pastBansEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - - for (let i = 0; i < rows.length; i++) { - const ban = rows[i]; - const bannedBy = await interaction.client.users.fetch(ban.admin_user_id); - - pastBansEmbed.addFields( - { - name: `${util.ordinal(i + 1)} Ban`, - value: ban.reason - }, - { - name: 'Punished By', - value: bannedBy.tag, - inline: true - }, - { - name: 'Date', - value: ban.timestamp.toLocaleDateString(), - inline: true - } - ); - } - - sendMemberEmbeds.push(pastBansEmbed); - } - await member.send({ - embeds: sendMemberEmbeds + embeds: [banEmbed], + files: [image] }).catch(() => console.log('Failed to DM user')); await member.ban({ @@ -146,10 +115,17 @@ async function banHandler(interaction) { reason: reason }); + bansListEmbed.setDescription(`${user.username} has been successfully banned, here is their previous bans`) bansListEmbed.addField(`${member.user.username}'s bans`, (count + 1).toString(), true); + bansListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + bansListEmbed.setTimestamp(Date.now()); + } - await interaction.editReply({ embeds: [bansListEmbed], ephemeral: true }); + await interaction.editReply({ embeds: [bansListEmbed], files: [image], ephemeral: true }); } const command = new SlashCommandBuilder() diff --git a/src/commands/kick.js b/src/commands/kick.js index f88b02c..c5b1edb 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -19,11 +19,13 @@ async function kickHandler(interaction) { const users = interaction.options.getString('users'); const reason = interaction.options.getString('reason'); + let image; + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; const kicksListEmbed = new Discord.MessageEmbed(); - kicksListEmbed.setTitle('User Kicks :thumbsdown:'); - kicksListEmbed.setColor(0xFFA500); + kicksListEmbed.setTitle('User Kicks'); + kicksListEmbed.setColor(0xdd6c02); for (const userId of userIds) { const member = await interaction.guild.members.fetch(userId); @@ -31,7 +33,10 @@ async function kickHandler(interaction) { const eventLogEmbed = new Discord.MessageEmbed(); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); eventLogEmbed.setTimestamp(Date.now()); eventLogEmbed.setFields( { @@ -43,11 +48,11 @@ async function kickHandler(interaction) { value: user.id }, { - name: 'Executor', + name: 'Moderator', value: `<@${executor.id}>` }, { - name: 'Executor User ID', + name: 'Moderator User ID', value: executor.id }, { @@ -55,7 +60,7 @@ async function kickHandler(interaction) { value: reason }, { - name: 'From bot /kick command', + name: 'From Bot', value: 'true' } ); @@ -64,7 +69,7 @@ async function kickHandler(interaction) { iconURL: guild.iconURL() }); - const { count, rows } = await Kicks.findAndCountAll({ + const { count } = await Kicks.findAndCountAll({ where: { user_id: member.id } @@ -76,19 +81,19 @@ async function kickHandler(interaction) { const sendMemberEmbeds = []; if (count >= 2) { // Atleast 2 previous kicks, this would be the 3rd strike. Ban - eventLogEmbed.setColor(0xF24E43); - eventLogEmbed.setTitle('Event Type: _Member Banned_'); + eventLogEmbed.setColor(0xa30000); + eventLogEmbed.setTitle('_Member Banned_'); + eventLogEmbed.setDescription(`${user.username} has been banned from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); + eventLogEmbed.setThumbnail('attachment://mod-ban.png'); const banEmbed = new Discord.MessageEmbed(); - banEmbed.setTitle('Punishment Details'); + banEmbed.setTitle('_Member Banned_'); banEmbed.setDescription('You have been banned from the Pretendo Network server. You may not rejoin at this time, and an appeal may not be possible\nYou may review the details of your ban below'); - banEmbed.setColor(0xF24E43); + banEmbed.setThumbnail('attachment://mod-ban.png'); + banEmbed.setColor(0xa30000); banEmbed.setTimestamp(Date.now()); - banEmbed.setAuthor({ - name: `Banned by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); banEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -100,7 +105,7 @@ async function kickHandler(interaction) { }, { name: 'From kick', - value: 'This ban was the result of being kicked 3 times. Below is a list of all previous kicks' + value: 'This ban was the result of being kicked 3 times' } ); @@ -108,19 +113,19 @@ async function kickHandler(interaction) { sendMemberEmbeds.push(banEmbed); } else { // Just kick - eventLogEmbed.setColor(0xEF7F31); - eventLogEmbed.setTitle('Event Type: _Member Kicked_'); + eventLogEmbed.setColor(0xdd6c02); + eventLogEmbed.setTitle('_Member Kicked_'); + eventLogEmbed.setDescription(`${user.username} has been kicked from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-kick.png'); + eventLogEmbed.setThumbnail('attachment://mod-kick.png'); const kickEmbed = new Discord.MessageEmbed(); - kickEmbed.setTitle('Punishment Details'); + kickEmbed.setTitle('_Member Kicked_'); kickEmbed.setDescription('You have been kicked from the Pretendo Network server. You may rejoin after reviewing the details of the kick below'); - kickEmbed.setColor(0xEF7F31); + kickEmbed.setThumbnail('attachment://mod-kick.png'); + kickEmbed.setColor(0xdd6c02); kickEmbed.setTimestamp(Date.now()); - kickEmbed.setAuthor({ - name: `Kicked by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); kickEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -128,6 +133,10 @@ async function kickHandler(interaction) { kickEmbed.setFields({ name: 'Kick Reason', value: reason + }, + { + name: 'Amount Of Times Kicked', + value: (count + 1).toString() }); isKick = true; @@ -135,46 +144,11 @@ async function kickHandler(interaction) { sendMemberEmbeds.push(kickEmbed); } - await util.sendEventLogMessage(guild, null, eventLogEmbed); - - if (count > 0) { - const pastKicksEmbed = new Discord.MessageEmbed(); - pastKicksEmbed.setTitle('Past Kicks'); - pastKicksEmbed.setDescription('For clarifty purposes here is a list of your past kicks'); - pastKicksEmbed.setColor(0xEF7F31); - pastKicksEmbed.setTimestamp(Date.now()); - pastKicksEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - - for (let i = 0; i < rows.length; i++) { - const kick = rows[i]; - const kickedBy = await interaction.client.users.fetch(kick.admin_user_id); - - pastKicksEmbed.addFields( - { - name: `${util.ordinal(i + 1)} Kick`, - value: kick.reason - }, - { - name: 'Punished By', - value: kickedBy.tag, - inline: true - }, - { - name: 'Date', - value: kick.timestamp.toLocaleDateString(), - inline: true - } - ); - } - - sendMemberEmbeds.push(pastKicksEmbed); - } + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); await member.send({ - embeds: sendMemberEmbeds + embeds: sendMemberEmbeds, + files: [image] }).catch(() => console.log('Failed to DM user')); if (isKick) { @@ -200,10 +174,18 @@ async function kickHandler(interaction) { reason: reason }); + kicksListEmbed.setDescription(`${user.username} has been successfully kicked, here is their previous kicks`) kicksListEmbed.addField(`${member.user.username}'s kicks`, (count + 1).toString(), true); + kicksListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + kicksListEmbed.setTimestamp(Date.now()); } - await interaction.editReply({ embeds: [kicksListEmbed], ephemeral: true }); + image = new Discord.MessageAttachment('./src/images/mod/mod-kick.png'); + kicksListEmbed.setThumbnail('attachment://mod-kick.png'); + await interaction.editReply({ embeds: [kicksListEmbed], files: [image], ephemeral: true }); } const command = new SlashCommandBuilder() diff --git a/src/commands/settings.js b/src/commands/settings.js index fa8e3f5..d2332b9 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -9,6 +9,7 @@ const editableOptions = [ 'channels.nsfw-logs', 'channels.event-logs', 'channels.event-logs.blacklist', + 'channels.mod-logs' ]; async function verifyInputtedKey(interaction) { diff --git a/src/commands/warn.js b/src/commands/warn.js index 87dca08..891335a 100644 --- a/src/commands/warn.js +++ b/src/commands/warn.js @@ -23,8 +23,10 @@ async function warnHandler(interaction) { const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; const warningListEmbed = new Discord.MessageEmbed(); - warningListEmbed.setTitle('User Warnings :thumbsdown:'); - warningListEmbed.setColor(0xFFA500); + warningListEmbed.setTitle('User Warnings'); + warningListEmbed.setColor(0xffc800); + + let image; for (const userId of userIds) { const member = await interaction.guild.members.fetch(userId); @@ -32,9 +34,12 @@ async function warnHandler(interaction) { const eventLogEmbed = new Discord.MessageEmbed(); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setColor(0xffc800); + eventLogEmbed.setDescription(`${user.username} has been warned in Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); + eventLogEmbed.setThumbnail('attachment://mod-warn.png'); eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setTitle('Event Type: _Member Warned_'); // Default type + eventLogEmbed.setTitle('_Member Warned_'); // Default type eventLogEmbed.setFields( // Default fields { name: 'User', @@ -45,11 +50,11 @@ async function warnHandler(interaction) { value: user.id }, { - name: 'Executor', + name: 'Moderator', value: `<@${executor.id}>` }, { - name: 'Executor User ID', + name: 'Moderator User ID', value: executor.id }, { @@ -57,7 +62,7 @@ async function warnHandler(interaction) { value: reason }, { - name: 'From bot /warn command', + name: 'From Bot', value: 'true' } ); @@ -66,7 +71,7 @@ async function warnHandler(interaction) { iconURL: guild.iconURL() }); - const { count, rows } = await Warnings.findAndCountAll({ + const { count } = await Warnings.findAndCountAll({ where: { user_id: member.id } @@ -79,19 +84,19 @@ async function warnHandler(interaction) { let isBan; if (totalWarnings === 3) { // 2 previous warnings, this would be the 3rd strike - eventLogEmbed.setColor(0xEF7F31); - eventLogEmbed.setTitle('Event Type: _Member Kicked_'); + eventLogEmbed.setColor(0xdd6c02); + eventLogEmbed.setTitle('_Member Kicked_'); + eventLogEmbed.setDescription(`${user.username} has been kicked from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-kick.png'); + eventLogEmbed.setThumbnail('attachment://mod-kick.png'); punishmentEmbed = new Discord.MessageEmbed(); - punishmentEmbed.setTitle('Punishment Details'); + punishmentEmbed.setTitle('_Member Kicked_'); punishmentEmbed.setDescription('You have been kicked from the Pretendo Network server. You may rejoin after reviewing the details of the kick below'); - punishmentEmbed.setColor(0xEF7F31); + punishmentEmbed.setThumbnail('attachment://mod-kick.png'); + punishmentEmbed.setColor(0xdd6c02); punishmentEmbed.setTimestamp(Date.now()); - punishmentEmbed.setAuthor({ - name: `Kicked by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); punishmentEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -111,19 +116,19 @@ async function warnHandler(interaction) { } if (totalWarnings >= 4) { // At least 3 previous warnings. They were kicked already, this is a ban - eventLogEmbed.setColor(0xF24E43); - eventLogEmbed.setTitle('Event Type: _Member Banned_'); + eventLogEmbed.setColor(0xa30000); + eventLogEmbed.setTitle('_Member Banned_'); + eventLogEmbed.setDescription(`${user.username} has been banned from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); + eventLogEmbed.setThumbnail('attachment://mod-ban.png'); punishmentEmbed = new Discord.MessageEmbed(); - punishmentEmbed.setTitle('Punishment Details'); + punishmentEmbed.setTitle('_Member Banned_'); punishmentEmbed.setDescription('You have been banned from the Pretendo Network server. You may not rejoin at this time, and an appeal may not be possible\nYou may review the details of your ban below'); - punishmentEmbed.setColor(0xF24E43); + punishmentEmbed.setThumbnail('attachment://mod-ban.png'); + punishmentEmbed.setColor(0xa30000); punishmentEmbed.setTimestamp(Date.now()); - punishmentEmbed.setAuthor({ - name: `Banned by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); punishmentEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -135,50 +140,19 @@ async function warnHandler(interaction) { }, { name: 'From warnings', - value: 'This ban was the result of being warned 4 times. Below is a list of all previous warnings' + value: 'This ban was the result of being warned 4 times' } ); isBan = true; } - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); if (punishmentEmbed) { - const pastWarningsEmbed = new Discord.MessageEmbed(); - pastWarningsEmbed.setTitle('Past Warnings'); - pastWarningsEmbed.setDescription('For clarifty purposes here is a list of your past warnings'); - pastWarningsEmbed.setColor(0xEF7F31); - pastWarningsEmbed.setTimestamp(Date.now()); - pastWarningsEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - - for (let i = 0; i < rows.length; i++) { - const warning = rows[i]; - const warningBy = await interaction.client.users.fetch(warning.admin_user_id); - - pastWarningsEmbed.addFields( - { - name: `${util.ordinal(i + 1)} Warning`, - value: warning.reason - }, - { - name: 'Punished By', - value: warningBy.tag, - inline: true - }, - { - name: 'Date', - value: warning.timestamp.toLocaleDateString(), - inline: true - } - ); - } - await member.send({ - embeds: [punishmentEmbed, pastWarningsEmbed] + embeds: [punishmentEmbed], + files: [image] }).catch(() => console.log('Failed to DM user')); if (isKick) { @@ -207,14 +181,12 @@ async function warnHandler(interaction) { } else { punishmentEmbed = new Discord.MessageEmbed(); - punishmentEmbed.setTitle('Warning'); + punishmentEmbed.setTitle('_Member Warned_'); punishmentEmbed.setDescription('You have been issued a warning.\nYou may review the details of your warning below'); - punishmentEmbed.setColor(0xF24E43); + image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); + punishmentEmbed.setThumbnail('attachment://mod-warn.png'); + punishmentEmbed.setColor(0xffc800); punishmentEmbed.setTimestamp(Date.now()); - punishmentEmbed.setAuthor({ - name: `Warned by: ${executingMember.user.tag}`, - iconURL: executingMember.user.avatarURL() - }); punishmentEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() @@ -239,7 +211,8 @@ async function warnHandler(interaction) { ); await member.send({ - embeds: [punishmentEmbed] + embeds: [punishmentEmbed], + files: [image] }).catch(() => console.log('Failed to DM user')); } @@ -249,10 +222,18 @@ async function warnHandler(interaction) { reason: reason }); - warningListEmbed.addField(`${member.user.username}'s warnings`, totalWarnings.toString(), true); + warningListEmbed.setDescription(`${user.username} has been successfully warned, here is their previous warns`) + warningListEmbed.addField(`${member.user.username}'s warns`, (count + 1).toString(), true); + warningListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + warningListEmbed.setTimestamp(Date.now()); } - await interaction.editReply({ embeds: [warningListEmbed], ephemeral: true }); + image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); + warningListEmbed.setThumbnail('attachment://mod-warn.png'); + await interaction.editReply({ embeds: [warningListEmbed], files: [image], ephemeral: true }); } const command = new SlashCommandBuilder() diff --git a/src/events/guildMemberAdded.js b/src/events/guildMemberAdded.js new file mode 100644 index 0000000..ca0fb9f --- /dev/null +++ b/src/events/guildMemberAdded.js @@ -0,0 +1,46 @@ +const Discord = require('discord.js'); +const util = require('../util'); + +/** + * + * @param {Discord.GuildMember} member + */ +async function guildMemberAddedHandler(member) { + const guild = member.guild; + const user = member.user; + + const eventLogEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/events/event-join.png'); + + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0x97fdb9); + eventLogEmbed.setTitle('_Member Joined_'); + eventLogEmbed.setDescription(`${user.username} has joined Pretendo`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Account Creation Date (UTC)', + value: user.createdAt.toUTCString() + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-join.png'); + + await util.sendEventLogMessage('channels.event-logs', guild, null, eventLogEmbed, image, null); +} +module.exports = guildMemberAddedHandler; \ No newline at end of file diff --git a/src/events/guildMemberRemove.js b/src/events/guildMemberRemove.js index 2dbdbed..94f47dc 100644 --- a/src/events/guildMemberRemove.js +++ b/src/events/guildMemberRemove.js @@ -9,17 +9,22 @@ const util = require('../util'); async function guildMemberRemoveHandler(member) { const guild = member.guild; const user = member.user; + + let image; const auditLogs = await guild.fetchAuditLogs({ limit: 1 }); const eventLogEmbed = new Discord.MessageEmbed(); - - eventLogEmbed.setColor(0xC0C0C0); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0x878787); + eventLogEmbed.setDescription(`${user.username} has left Pretendo`); eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setTitle('Event Type: _Member Left_'); // Default type + eventLogEmbed.setTitle('_Member Left_'); // Default type eventLogEmbed.setFields( // Default fields { name: 'User', @@ -34,6 +39,7 @@ async function guildMemberRemoveHandler(member) { text: 'Pretendo Network', iconURL: guild.iconURL() }); + eventLogEmbed.setTimestamp(Date.now()); const latestLog = auditLogs.entries.first(); @@ -43,7 +49,9 @@ async function guildMemberRemoveHandler(member) { ((Date.now() - latestLog.createdTimestamp) > 2000) // log is too old, older than a couple seconds ago ) { // User probably just left on their own - await util.sendEventLogMessage(guild, null, eventLogEmbed); + image = new Discord.MessageAttachment('./src/images/events/event-leave.png'); + eventLogEmbed.setThumbnail('attachment://event-leave.png'); + await util.sendEventLogMessage('channels.event-logs', guild, null, eventLogEmbed, image, null); return; } @@ -58,16 +66,22 @@ async function guildMemberRemoveHandler(member) { if (target.id !== member.id) { // Log target does not match current user // Probably just left on their own - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.event-logs', guild, null, eventLogEmbed); return; } if (latestLog.action === 'MEMBER_KICK') { - eventLogEmbed.setColor(0xEF7F31); - eventLogEmbed.setTitle('Event Type: _Member Kicked_'); + eventLogEmbed.setColor(0xdd6c02); + eventLogEmbed.setTitle('_Member Kicked_'); + eventLogEmbed.setDescription(`${user.username} has been kicked from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-kick.png'); + eventLogEmbed.setThumbnail('attachment://mod-kick.png'); } else { - eventLogEmbed.setColor(0xF24E43); - eventLogEmbed.setTitle('Event Type: _Member Banned_'); + eventLogEmbed.setColor(0xa30000); + eventLogEmbed.setTitle('_Member Banned_'); + eventLogEmbed.setDescription(`${user.username} has been banned from Pretendo by ${executor.username}`); + image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); + eventLogEmbed.setThumbnail('attachment://mod-ban.png'); } eventLogEmbed.setFields( @@ -80,11 +94,11 @@ async function guildMemberRemoveHandler(member) { value: user.id }, { - name: 'Executor', + name: 'Moderator', value: `<@${executor.id}>` }, { - name: 'Executor User ID', + name: 'Moderator User ID', value: executor.id }, { @@ -92,12 +106,12 @@ async function guildMemberRemoveHandler(member) { value: latestLog.reason }, { - name: 'From bot command', + name: 'From Bot', value: 'false' } ); - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); } module.exports = guildMemberRemoveHandler; \ No newline at end of file diff --git a/src/events/guildMemberUpdate.js b/src/events/guildMemberUpdate.js index f524d00..4ef119c 100644 --- a/src/events/guildMemberUpdate.js +++ b/src/events/guildMemberUpdate.js @@ -24,16 +24,20 @@ async function guildMemberUpdateHandler(oldMember, newMember) { const eventLogEmbed = new Discord.MessageEmbed(); - eventLogEmbed.setColor(0xC0C0C0); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); eventLogEmbed.setFooter({ text: 'Pretendo Network', iconURL: guild.iconURL() }); if (oldMember.communicationDisabledUntilTimestamp !== newMember.communicationDisabledUntilTimestamp) { - eventLogEmbed.setTitle('Event Type: _Member Timedout_'); - + const image = new Discord.MessageAttachment('./src/images/events/event-timedout.png'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffb663); + eventLogEmbed.setTitle('_Member Timedout_'); + eventLogEmbed.setDescription(`${user.username} has been timed out by ${executor.username}`); eventLogEmbed.setFields( { name: 'User', @@ -44,11 +48,11 @@ async function guildMemberUpdateHandler(oldMember, newMember) { value: user.id }, { - name: 'Executor', + name: 'Moderator', value: `<@${executor.id}>` }, { - name: 'Executor User ID', + name: 'Moderator User ID', value: executor.id }, { @@ -60,13 +64,21 @@ async function guildMemberUpdateHandler(oldMember, newMember) { value: newMember.communicationDisabledUntil.toUTCString() } ); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-timedout.png'); - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); } if (oldMember.nickname !== newMember.nickname) { - eventLogEmbed.setTitle('Event Type: _Member Nickname Update_'); - + const image = new Discord.MessageAttachment('./src/images/events/event-nick.png'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xb6d7ff); + eventLogEmbed.setTitle('_Member Nickname Update_'); + eventLogEmbed.setDescription(`${oldMember.nickname || oldMember.user.username}'s nickname has been changed to ${newMember.nickname || oldMember.user.username}`); eventLogEmbed.setFields( { name: 'User', @@ -80,10 +92,6 @@ async function guildMemberUpdateHandler(oldMember, newMember) { name: 'Executor', value: `<@${executor.id}>` }, - { - name: 'Executor User ID', - value: executor.id - }, { name: 'Old Nickname', value: oldMember.nickname || oldMember.user.username + '(No Nickname)', @@ -95,8 +103,10 @@ async function guildMemberUpdateHandler(oldMember, newMember) { inline: true } ); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-nick.png'); - await util.sendEventLogMessage(guild, null, eventLogEmbed); + await util.sendEventLogMessage('channels.event-logs', guild, null, eventLogEmbed, image, null); } } diff --git a/src/events/messageDelete.js b/src/events/messageDelete.js index bb4ccf5..4cbe9c7 100644 --- a/src/events/messageDelete.js +++ b/src/events/messageDelete.js @@ -7,7 +7,6 @@ const util = require('../util'); */ async function messageDeleteHandler(message) { if (message.author.bot) return; - if (!message.member) return; const guild = await message.guild.fetch(); const member = await message.member.fetch(); @@ -16,10 +15,15 @@ async function messageDeleteHandler(message) { const messageContent = message.content.length > 1024 ? message.content.substr(0, 1023) + '…' : message.content; const eventLogEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/events/event-delete.png'); - eventLogEmbed.setColor(0xC0C0C0); - eventLogEmbed.setTitle('Event Type: _Message Delete_'); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Message Delete_'); + eventLogEmbed.setDescription(`${user.username}'s message in ${message.channel.name} has been deleted`); eventLogEmbed.setTimestamp(Date.now()); eventLogEmbed.setFields( { @@ -31,21 +35,9 @@ async function messageDeleteHandler(message) { value: user.id }, { - name: 'Author', - value: `<@${message.author.id}>` - }, - { - name: 'Author ID', - value: message.author.id - }, - { - name: 'Channel Tag', + name: 'Channel', value: `<#${message.channelId}>` }, - { - name: 'Channel Name', - value: message.channel.name - }, { name: 'Message', value: messageContent @@ -55,8 +47,10 @@ async function messageDeleteHandler(message) { text: 'Pretendo Network', iconURL: guild.iconURL() }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-delete.png'); - await util.sendEventLogMessage(guild, message.channelId, eventLogEmbed); + await util.sendEventLogMessage('channels.event-logs', guild, message.channelId, eventLogEmbed, image, null); } diff --git a/src/events/messageUpdate.js b/src/events/messageUpdate.js index 20f9fcf..c4097e2 100644 --- a/src/events/messageUpdate.js +++ b/src/events/messageUpdate.js @@ -16,12 +16,27 @@ async function messageUpdateHandler(oldMessage, newMessage) { const oldMessageContent = oldMessage.content.length > 1024 ? oldMessage.content.substr(0, 1023) + '…' : oldMessage.content; const newMessageContent = newMessage.content.length > 1024 ? newMessage.content.substr(0, 1023) + '…' : newMessage.content; - + + // Jump button + const row = new Discord.MessageActionRow(); + row.addComponents( + new Discord.MessageButton() + .setLabel('Jump!') + .setStyle('LINK') + .setURL(newMessage.url) + .setEmoji('📨'), + ); + const eventLogEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/events/event-update.png'); - eventLogEmbed.setColor(0xC0C0C0); - eventLogEmbed.setTitle('Event Type: _Message Update_'); - eventLogEmbed.setDescription('――――――――――――――――――――――――――――――――――'); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffefb6); + eventLogEmbed.setTitle('_Message Update_'); + eventLogEmbed.setDescription(`${user.username} has updated their message in ${newMessage.channel.name}`); eventLogEmbed.setFields( { name: 'User', @@ -32,13 +47,9 @@ async function messageUpdateHandler(oldMessage, newMessage) { value: user.id }, { - name: 'Channel Tag', + name: 'Channel', value: `<#${newMessage.channelId}>` }, - { - name: 'Channel Name', - value: newMessage.channel.name - }, { name: 'Old Message', value: oldMessageContent, @@ -54,8 +65,10 @@ async function messageUpdateHandler(oldMessage, newMessage) { text: 'Pretendo Network', iconURL: guild.iconURL() }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-update.png'); - await util.sendEventLogMessage(guild, newMessage.channelId, eventLogEmbed); + await util.sendEventLogMessage('channels.event-logs', guild, newMessage.channelId, eventLogEmbed, image, row); } } diff --git a/src/images/events/event-delete.png b/src/images/events/event-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..43ed3087795573b48f04b3f5c3df25bd63fa251c GIT binary patch literal 3372 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 Jxvp#C{{SVX9&7*r literal 0 HcmV?d00001 diff --git a/src/images/events/event-nick.png b/src/images/events/event-nick.png new file mode 100644 index 0000000000000000000000000000000000000000..e15009e7c2c3ef10b5195a2f4861f5c4e9b1a1da GIT binary patch literal 5335 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 AQvd(} literal 0 HcmV?d00001 diff --git a/src/images/events/event-update.png b/src/images/events/event-update.png new file mode 100644 index 0000000000000000000000000000000000000000..64bb67183275809929286d0cafb0b8a6a98a014e GIT binary patch literal 3653 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 literal 0 HcmV?d00001 diff --git a/src/images/mod/mod-ban.png b/src/images/mod/mod-ban.png new file mode 100644 index 0000000000000000000000000000000000000000..c85ab54bf61762d12502a0b1eda50f19378974b8 GIT binary patch literal 9032 zcmds7dpy(c+o#iXno6b8(m|=5Iw&`WJ##F0>qEwXAM9#60!;DEM zr<^y3m@{M8hGuNW^Vw&8f4|=!&-1*Vzn?$6ygvKf_jO;_`@Zh?b=}wX3B6=yxPHx! zH4+jM>y0iLSV~Ap=7|2JmI1#URxi5-{FC&yG}M>utWgdI9#(r^u<@3VSSus?laz>! z-zFikchbl}@3LR~Bs~4QCwB=tAVZo9GS`1S2o z$~7;Xt`4kIP?lWvb?aFv$*z4z7E)$(WzI!A?V;r`1@0B>_}&_$r+Id4>mXJRa=kja zT|&2d6;)nRVl){qp>GJ0d=vp)CIy47*Z_x1?M9I2aCwFy0E|!c$&74L|lAUM0VAl*U-}R-t5fJ{Ax+Jdxzd6 zex7G`AR%nxq!mHq{$08Q;j+2G23X)BM}+*z`*#zR)@3Q~eF|MvNw5+<2fePz7Cqxq zweCnDybryx7LC44P`?k$P<#kob}21U3NEGlE@1EIZV6{*XcAuHb>{!}f>n#k{FTX^ zlU{`~Q-?}=x^Is^eFa9|P&FAumC=J#bP3*leSjm8)@>kmwr#{bY|>kF{$ zJNABjM+jA3r|&c-wn?wIS#P%mx=8{qr4R<)p$mCn(5$z7boYx48FW)Hdc%dyjga+8 zc-`dDBuUW|O#MD|S!gnTRnvbz?I8TN1Wf%y*J2AkDIB}iH)yb_0&#xnb@rNK z_egro^33*oG{eIwxuIw5Hj7NHM7m%QxiIYs%guhbwmDMbU={_?Ba_?1oT*n8adMf-KHASd|HH-G72(lNO;4znZ_-0Owts=E_Gl1O0NGQ z0DubzHs68}U3cPuBf$<=QQNW#@q|G|rK7(z>9LItY_^3|=%@?M=O*`TCL`+*^c`7^ zc~tS8)(Cr=Wmz*J^xCPPslYq6=C7!X$&O z>4ypmu0XCIz?-*_G*_k5(&N?Px9>aY2X$Be82P$1q)*yy&^}ILS~>mGZLWAkJD@ ztlcS>h|r0ieFWXH``wH8(8Sw2rza_6F{@fIw6ybOz23u4KKem9xmSxX^MC)m&UNn8 z0u``^&k5gms-5XNH7+!1ZZa-Q?Vpcq@#mhiD~`QLcS7li^*jLg6ZC!daD27c``{0{ zuETCo^)WK&E6#tnvvZ>=C}|PHVewxhQ0!Pp_8?P9rm|6KAJe$_W!ywL*NE#pZw8`q zhTR8f&L@@99FNWWRt+KgfBvpxW*5`W_AI3W{`|tEngP{Dva$%tU2rQ_hMJiZ8x_V} zTS6OrvyuCzQxe<*4^6);crNZ9vi|09E}w5OOZbt-U*hWa^7)|V~X|) z?oH{n;7ZzP4(9P5u9fPeeO)%URkPISiDg%}Svr0`08`gHwucMtv5UK`)46w>hhRGD zuDMxt18NJ;&-11qIJ*>vHa8FKGIfoC7DZ$vjo!`47YF$}CG<7VM>h?+|cdCHU|A1;E+%gE)Uef!9ouzxn^70)8?vp*iTSB(9 zKL;Ju8p8f)o=LFXR1Dp9iGW>EG41o}F@4S`sG{xfB4d5}-pxzl3}0XK`Y){ac0vp# zmx6xja4}U{sa(Yvk zq?P@OWAXf!)uh36-uQWNb`|XNoUrh&&~ni2^$f>MZKHkfOhwt4h|9S7_=JYA_u)@5 zBQ-uyY@XBGJZFY8RCPL zrA_ud-bFc~2P&xnn}~}vY;!P4r7Q{DQ=|5-&f7f&*wsPlF)EIi zF(=CBzkny>QVVRs)a$D7vQhQL!B6~A-q56*QBZRfhmd-!#n@1!)xl!ry#=hd^-Pv`^GdmvzM(Hx;3A+JR$#3rQ`Xdt$DfvrpIQnj^VlpM!0Fqj`bmC>ND!|wSYUoP zqc@>vNLRY}iR{>TII}2Bs7V9KcVMy)6%??4iMDe)@)BXu-K3a54=kV^F37P1VL0l` ziGIJGE0a`Z_GjUIX|wYB#%$u1NctIdnZae?u7>5|)&8_6y!9Q7t~_O4G_+9H^pS7N z?U;Fd{@L0KpmXFx&Ct;mqrA0Y#ia$>soFyNoRUhzqcK6pa##K{0!Y&`*@jGD)_p#;Xy278JhC{G*sFa)>ZF6U8`ceY) zb2L@!LrhVK(T1;bjgCE(mgFgFas+N#pwe15aR z4c>+DI^H*OhI7P;e3-s72&FAWXR`enc(i0KpkJHYLZ zLVs$tA?@thbv&;7b%vIV)a2gTYW(_qoCT>+bwh`G%X8Omb`=} zc?0vDkDL(ed^!IK2!;YI@Mzidxnlm6yi7oa-JEb{n-*g8}DivvFgr8=scpd=3UO0;Yh zk$YHMW$H*7|LA0-p%eC<0xH$(YLHi1euw?jq9{=BWFJoHb+b>wa64v({PM0+XW(8s z;OD%bS?~0ZVy0&yw8%Tb3#F$%;?}v1BUQ7=EnJHV7#R3~r9Fm2f3B)%l+FU?54b;! zTex=n!0(G<7HZS;9qJu@Zhua=t)tD#Uf`EnO`iEjGl5@aVI>wQA{_msa&leoP$K{G z=QrZb&cUZ=4Kk}f>u8%gi_9&rJm-Ue+r<5c+UPg*_{A|LN9jK^KujL+8}7~8=%Fg6 zHd~1NwV6t#=Y=seX?zZ4=;vxgzKM>AMFiwFT>{MVKW=9Yh&Mo*h>so{ zQ5-v9`#6|?laeM9epHs`FlKoe=6@87hk*CbuS@UL5%DWvmM7qS^0(H!lzsL0>`~&0 z+47x>r_@Re{WksVs-m`k*Qii4=v9sRJ0MX7LydF`w!h#^mBv4xyU}Wms%`ezcNUC9 z_aJPBYiV2r7hu?zpI9qo7u>u&f{slGh@GgrGWd>XcAC;ect9jaVc}E5`E7CMC`MMV zps_&aF$CQ;WX(d@J`M2bR*3(YuQzn-_`~w%zlS!-`<=OhMoIQop)+D4QAwRD`&}tJ z^R<%}XqFLt4q23e3lt0%pYxzxq}z~Q)MltXMfB7}D;R^R{Dl@h_mHAyrY_TS<|%NU zA38VXi+mweIro>*syds8E_&RmV-j|#os^~Y3By!&B+Yy@+&ZB=6nq=mke;g_v%nhk&pQAKn%6394%oje?NUGQ;YBmVMylm9-1g--2* z1X6|Kqx5Ye|JidLB0rq6$&dZAHiNSnsA*g{pfIspBNxB4w>QllynX{i$h~Ffs|)*2=5f@{MStUzFk7p))G_ zbK@lwad=m74{9_!Sh@BCb*`MKp&k>tzd^OhKl4k@z5W#MhuA1LaFv76Q0=QX@iKFx zPIn!Z#YzK_zq*IYX+;Ze|NEDYp=FwLhPcyoIEbN_52uI!f$t^l1|%hibFH3%9%m1W zzpYjvR9xtf_}SbrRbmDdqJWx(m(Qn~lR{3Y=e|W#P`e);m3B{VQ6q8J>g&%I98J6d zo~0WTNi(O5jqs4g#D<^PFi_{PV!V0GZze;+v#2|iC&pm22ZXpi(Mkt$fB01Hp`NGU z(q%9-ct4cXpN%ouGy7Me37C^yu2fG$G_w9m0#M0vZCgwiFqo0`@gV~pw_U~gejdg% zxxB1)_Bh#7J0ZBJqs-ZJL^Fme+`&_#6A{6`7QL%_3{a>4Efd}Fn#^S28||slPZ;}@ zKMIQHqDC5k($%X|KS29{TzG7@S&dZWq)8mgQvAM_g@f<}8B5B$?itdZ1Xnlx1zG_< zddA8nlduH|9D(Y!B`*bZRnFadC9NwiifAzaKmrf3Gma^2ToYWDpi*BH@tso5;tkx?RMGi(jfNx?3`g)gTa1a{G1w$2 zeL)!aos;VBPVogy8h)zHW>Goeiq7?b^W!yru}O)Wa8V7<_j{h0vIhMqph=GrQF({! zou&~mbM9TjyLd=I!3Xi{Ry~IK^)h65(F6Um=&y5c7vBWtpv1(Jfr7<*s3KXs zdfA5b87AagET#9uc=!WqZw8!E#4& zL@8%qn~4jb91(a7;(!_~?Q+1LdE?O{F>j{g8_Zn=)W&STgxqIY@~h<%#>jQs**WGe zDnR)IAgdfGyh3eV7Qy!DbPk?1l|eLmhHL*;zq1%DeSYYu3~iWp6pWS!_QE^N8zGIT z>2gxUoD1mkhkTn(YoBS?kbTElyr$p1@t%7BUF!-NP%a$`7bO-bTjkMf`etnhsNx9r zA^?)0GvdlrjN92OZ}Qc6zLbCQ69oh*p@^;4Z9tPAUjVn zXUfAzO$sA%PD^z(d&iwAGw;4$mi9mw9tZt25#4l}3ZKRdc3Btnn%HZbqENQR%({{Y zkSujz=!laHHCxKy4uSN~udwAty&>L2&<1~+9;3AXTbdf_rjE;d8c+-D|HgDJng8?w zzxc|ex)=)Akgm6H)0W$ZwczH5obbi`A)R?qn&)*XNK%Z^&~-?H=C~D;;pEg!RL$># zq|+kH?}AJ=U4U;md#l2+UKpM-f9EcNZ-F@h`eWH-(u|+8fpF;A`SC;V>56zQN^dn% zJcopDz|%sBpUs=4R6iUm&?<}jy@RKSJTiBO97l}H=f5oaYci&Z6y*7P0J+WsWjr|9 z6{|JQ?LuQ?Bb^wPkD`s!~n@fHEQSQJ_QE<1zp z3+uDzO(?HUorNiw8{^oK@)hmwOs)pq#~X$Q+8?NkDqJi0L&Uu+{_E-NYk%rm8eji* z_hnX@uK;0G_VuinhNP=)U)V4|?$LYnSrH5`C|0J(jg}bEj}6t0JlhCjkl&@p@Eoru zuj<`x=MD0GcYRw9tG&eW4n`>llb?#$zI$MsxRTlmb6Z?;9V+5yst)a7AenGfZ9?lC z3m(CBsnuQ3rgg;~Q+mL0t%n$z07!c`-0#b%>yY*>VIk){s1`p3Z#qbPDKM8W+xj!*j2-^~~ zB4#44tC489Pw-|IG5x_f=(t>GNV`dKB;}G@24wFFpl{LIruwE!3^LF#IKgtx|6v@J z z=1s&8JH7ptIp1W4S(0n!qyaeK+hTk%!-BH~fTh=QF9vWfyaMtTsiCeTWFUqm;q&Ci zm3tis0V)XLMY5?@AMR)SG>y!(y19zIvqerM;O$OczS$yc-zarEP}}ZWDA4072YPX`jxnZuYL#@&Vt0Bu5t_q>kk( ztIK+9kM+-hFOJ`s$+173%iBt415LmIoH zHztGuG_HJ!b{=wPIIsAN0+(07t`l-2w5}$2F42WDyhi%cIPSUN4Xd3TTqh(uActmm z{-3&>D>eb~<$r38Dvl;C0bSICiPHbkQw5u&;ItPeqeVcdu9Eg+g>dh{M>T66HN4hj zM#^)#J1yhDCN1p&wSQzIS6-6S7U=DD#W#E*wj&wBRtQ39Z}xFWSyHZsS`7ErtHvBhiq+&6HhpHZR`(|Imj19~#%9PWK`5+(zAd zs;Mo^P^o0Ryz?~k+p14F(#KbF4OTQ)dLMOIxq7#6qwEgwHIJ%D3t4~74ns?)%4{@% zRB6`9T5ae0FYg#CK>L0y_?Y-~H+)poiI3d*<$$8A4AV<01E0XEl_Q1msgfVDDu}y2 z2Hp`47ys+`B?2AeLoI)PSTX(pU&BZkoij7Y)xY-ezW~K|f?5Co literal 0 HcmV?d00001 diff --git a/src/images/mod/mod-kick.png b/src/images/mod/mod-kick.png new file mode 100644 index 0000000000000000000000000000000000000000..de9c045b55495f8dca9b53f23435c5c678b548ec GIT binary patch literal 7076 zcmb`McQoA3zyEbsFVR--b#8 zK}3}6^ZlLQU-zE-*FEPsXU=Qp`JOX#&YYRo`!UHz23lmqKw>N`EHWK!brUQsY{b8X z2=}2S`xOTNp~4O{(Ne=+92Sm#2mpTCR)JVpBsBjPY^;Jp1}rR`Ya=}~4H|k$Ar6q2 zBFkH2s&+fTlpEfPH_lrlDo+I#L3R)uvskhYU4j-p3zL`t8>q_;&}IwZVH4qE5gl+M z{Ng~!%_>UIDA8a|`q_$_IqkvhZt|>tN=zY-feZ{1GQ5J%Bst;wG))ka zMr)E>eHtK8TvM1gNt?dhoMOO{P)nFMTn&hLMz-LAGw+Fmup|pr1=2A{s0#8`T9D7X z<9;$HfBqQwSdjk_y`&;PzrQk5nHj~5JMITF3SVVr1wMf|O$M|J{)jU{j0OV-v#7Ts z3kpKq3?cDQU@?>6dS^_z=!H{cOl2(26{kr*;fD7OiWjHBAkHO}qxUFF?~xpzK!FkU zH&?t03-UA_x+g+>^;V>>^&eSDaWFH9s|fHLh;c_fVYCIazffnau_Tk=5^Av_ZnYtn z;ugYq;&eL@rD)To>d^Jr6FJDRe{m#?e8MQoB`n4z1d-;D<`JB9#Y=xmo1sfP>4sPP zj1=RA{oaJqS(dHYhQwKpjh$J{Ri1U!1>aGIjhj_OSCr?e2v39>F!V8ymrW#F|524C z85)YO4dRK`U>tHHcqYa1Qk{XHT||kWKST}aD#ywM6ju}CoAboc7vZ*+<}eZG65$k% zQD+e502Lckrah(A6X7uw2)9)bS5Y?qUv`fd}BysCdrv+NDY-|^-*SiV?g5| z!v;5?5#thelw~uO;L-;1#%t0CsxUD!irazN>zJ_=I)S@ppqp$(9 z`zSKY^9d*m@W0Zgud*OF66X@;5cXDL_Eu!>wj(l^;OwvkwAvE0GKuv#5EU3w>4@-v zc?2z`IIW~OMLC7L>;Ve={5=i;TWNMjS$2RY*ESXw!xJ5KRkP5-qoS}#T8-i!2nPib zyMruV_ABbVv4+FZpW{C>GjWO7aW*sk81S&gH>jx-u|@>mEEA=0Cb8pw2zW(IjGLxD zL6t}aRiAP6b1-n3IbHaB6$vKPyRWhj3b7R}vhQB|y;fUgfAP2LcUybgz9n3P5>NI2 zM$I`)J~gH?9BQH2Mku>m0@b$-HD3}{qbWn(XJj3$WTXMtTs`X3t}!ERasqo4SoC8M zJpO8Eb%`Mx6RL@P+~RGCAuAgUH)obpZZB|1>#3&^(ueGNMtn+yAA-V<`p(d*#1A=& zQw~hy#)k@l_9Tb2lS;htqc&wZAbITnlPYYH@-KFMr;^XRkIqAgyo2ukmb95-u6%Z_ zeTDu+s9uy`o>JUD!@zD$jfyJMBfF89>)jH0Q>H&}mV6EfRfEdGXSfXsL=6dm|0wzS zLvRgqjP?HSYMthh=4$k^2sZFR{ZwiTL5~fO?-%4}E%CIFXMtG5lp-$nqO^$X6ll56 zTO0KazI9+-YapLpU$!^j+v3_t)pM`Rc00}s4qSu1s2-HbxSmu z4`jZF{SAIkG|)M2ohCGVhiP{)sBY6~#P=HCXN-F>REh{I zy#XmnbPvVWlC;h;h1gDPjay7dVp^iJPK$@$AqFx+qrajQ+gw_-KUB|b=)$k1n!W2c zx`y5&cI?>vmQp@=-yIL9C#0rYg!4>TAxGSgx6l?_n$@-{3qy0;6+8dvj#>R}@v8nf zy>tGT(hyn9AN<#7Ug^(k3-ke7(3qIQrN@)bI_?+Vu$KOvU&}2X#WCL-!75u*cXWZM zq*Es$|1pc7TKV?b&im+O8*dsOpp_#Rl-K4r4vz&>C6-7goEVFX+1XUx(7;{=4x%QClS z)27?*g+}9~9|m~ld2T;-XII{K&>wSaTt6Lr0CS=8ghZ6Gz4~Qe-74^sWJp6L8Wv{z zX+R9KZ^b-ooqhvY^1CBX4I&7C;(g#SLlD*#9s6sEHG7-ySI3aRhkJ4~tC4akC1#(# zIbrP4@##tb+Zk}Dz?rwSe@f%c9#_?;M|P0ZqMWiERG8)0?cE$2hfT6#GE|@ej`FqX zKp-&!fjh3X$*XKx(*=B6QFHR*=gRuBZT&e}a}IGe0yeJjTmJ>R*3R?|ZF1A8PdnOG z>rd0}>v6#{B)J?=NAA02sZDSYpkMOE*zR+`nZ2?A>y4(1^3rtWp{Q!Ti7eXWtMQkj z(G$j7<_W)uKO(aJ;WZ%Y@I9<`I{>Um{)h}mYF=wGoWe{0 z>5ET4SzOv-xb3p6m@_>0{-elZHXVm(yoN2BlDtE_Kb#)3=zR;_r7+2U2f$w(RJM4; z`dSe+vAEPjE<>B5{Hm+MP4^_wKJs;?Ij@2zqXdL3C4YpeJ;g%tq2oE(gi$c#)Llr~ zxQGkV#84v0FgZHp-BjbOiPU&N@Yk~B<>h9h#eH&AlEIl zZ}Bd2Q0cp9t55hLOyy4S*X=4W-ym_AE`ok_L%DK+DaiXMC;541r`L*zv9vnULi|b8 z#~%Uph6fAR31$_je7?RuvQ6oJ=W5YL5xyha;e~O3MFHUOKzKprP2QDW*2#i=zgdJK z_)?4o(!jNUEZO{xc%CF3v72wL`_Va7gn9Q&chP-1f4}v^Y@d@CZQzf)$I6%1ERe;i zjqm;YEaQMV;$(!#s77^$nta|zdF1vdaWY3ZxiqaFKad(ST752&%LsRV?k#|)I8N&B zLtXud9CDi{c;?F~cx5yMf7ePWo%ZjgET>btXX^wpTQ5@snv@K$tb%qm@As(*&2-|iF+KJ?{t`18kiOn+feJEK5|q5ZGwq&QfI)i%!n-E{FLYMm2Cn$B%2xZGl%(CDP)q5@ zLfvzbcF#Gcp7%+}0KKA!xlp;-mSd^Cg=SK@goR5iOR zBw36pIr#g99u%@$Jd)A?|S3_a$!n2&iGm4f6gASxyS-i$Usv<4kYB$%L8 zS6)5k=Kl)gjTOcFb#>4RSMw60>KL46xvm_{-_v`Yl&*f?<0G~f)K1=nT*ZY3GbSq= zOu}#WxmbPU=rFCcYF;EAdC`k{=Z2vh_<0W8;RjCb10bJ-&r5B@DIKo7*MTWWx;%hA zUV(6kK-}!yv1-xbVaO|#$hu2M^eRoY+odXl8oFt*0kI=uM+GF1qhYknI!_mRi~ zQhG%Fjwxl;d~z=I$*HOu`q7)F5p5U~rV)%RHUa1Q@a4D79egSb7;@jgv za`*}}VRN-6xLD;|i|g=RJQ_yx6cXle`PU_IBl^9I(2xybR}%!Z?fOa8!>c7qc5J46 zpH89Dt}yJoycnw#dTcFiiY|2W+Bt6Homm4kz_*_^xN#X``x@Cp+}ujg-~BE1IJK7z z)jaG2Zx5jS2of7xgEd(v*jIg(0H2rvLK}N*<71}g><|0T^08Os9_&N%aV#%|_*zo? z#FjkC8i?L7k-i&9J=PM6WBEM3c*MHHGUQHjnA)SnXa3qm>4#wtVb3v#?|`f(Wbtyc zPA}27#OVN(qx89_>vKn?Dg*3xQF&ka!XTz!5ZTnAtd9L%%3zau(A}#x>N#riO0Z6^ zjqwOO6#R!~S44)SmMuLYf*mze?C%^V zZ*qiLX%ZF-yBY)eCIM=Y^_Y96##5$K`StX9K}Xu`!*aPFg;Yy8rkR#`>D^kZb9G~v zSmZGQmpf|i2d|p+)wzl;?r zz9eZih{I)Ua2q?^+m5hf(HTiNcN9EbIC}Z;!S5T<{B)JV2Y=b`iC@^l?mHa^TJF_v zqbAnRo1Vha7Z+HwPFW^S%S3$EWo zHe!QX{YU?HeV{I$0$YjwRluJ0%cWDM*wIw+<2w0*57ENXs&BGX=OaWC`sbk2}Y54kp)5BE2 zOE8&#lv5_=i2jyLCeOm1ogY&vO3+nfBWbpnHQdoSKj^!_m>5eJ7&I@PLQ61AGX7zw z`mjkWR$RZV0Zro?u6JH-74|d0R-kOQPDotKFr|}{QSVUK-Zh?a$28Z{bFabzfSxq|WN!ULNzNXp7`>P7T47<1f&|Bktp;=vux>zhU`Y@E3U|-@7Y> z=tPIT)DT&Aqj`-iMp3*2uMnQbtZ+XG+ru6RK3Ilj7%ky!Np}1u4liui_&U4q-z~wC zKU|X^k+6O|aHmF@ihsO!C*&Yg@5&pf9DSUp$QsO*ZqUUGIJDVn5v>g z_z9G$GjYE8g8NjrV`qL7UxE_b45?b8TX1xCr9 zr+g@81#OtsZw$}%j5nV%L0)-8yDn7?(gA#OR&eM%`ZCOE1sOKrNkU$!WZl@AMm62b z*lL?nzZ4s1_&xXyNhXyjhHtw~kQ4!GQ4sg-1Au$HoPRe{y2FmXqR64|{w*hvQy#|AcF_*)k z!cU)gllb@yu0TksqOo=Z7CRZ7AYxnjn0^(yV*K6BENPZ{kn0d8y1LbF@Fm&0UWe<$ z6^w89R(*W1){?%l;qq`XCpSnun4{+?qAW*CZWI~JjTu1JX6R)6V~<5aoFzan8&*c< zM?Sjo^s)evz6dwR8yVQ7JxfS7UdeZtk8Cp^?M^1S)zBCm2VO>3aZ zrsRXws7EX-_ZMb_iH%UmH`vVCD}H~Fvhk6JOck>#_t_sKPkwho;sajHO&tp>YSj!Z zm0_+tVT(oGmyr2D{DP||m9R5%!@j1dkLcNTItYGF>P+QCHM5EwfKiN=B6WPEt%FXz z%q);^VGlVDiH{kJ)#8$o%0GWbB;HteTv(P&S}7;gWA=&Bb28KEsjJa(v#%N3f}_Zp zcD$wfU#`&U>CxycczvKxwv%B1kEJg&g5x|IME~S!`d0IG1TYTUa}?OjfAuPmEa{ys zyvY*YgIT?C^uUeqDJL1(+}wJH!$IQx82azXuSV)_b~5eF#}9;c_4RW#Pr z=4Ycu7s6!#uuC!|{%;C*lcqMc?y0_ec@8JGU<6c3N?LC*e=OYMzbg@LTMcz;4xQL*p2V(Zs~Si*h! zT6+1u;PF@H4|(_CsC(A6tJG%3;4HridxnFL3)``fg%CHgCKdm4_F2R>uo3^0lRB~G zLzbQj8d6@bYVT#vZrVYl|ICJ<+WGxODx@x_a<*@c*$KE*@m-B+2U3F}b5yQGf+pdQ8@fin9}96~UN}peb{d6` z8}4~qy{m8MDeDxqv~?-|8N|H{ZGw8$!EaZRCh2m*KV-}7R2|d6d0H)(fLa&$0kcAY z_lOFm@^5FR#O;)&f8s%Ia-M7aAtvVWlXT0uf?d*j(=nq`0EF`>)w~1 zc@F5Gs(GT9au40z623HMZCpTh##i`B?6Xwf3-)=U<@apjb2(3=z-?fOPjRI`HFs*v z-Zw)*zQ^94jiJDP=hf&$rh#ayDEj~l<(>AEq1=e1m;5&ym)_aqk76v!II z-Rh5?-}&_^FC6D3d}{8lhY4zA>!DU`Mcd6RVJCAeng!kCS4@9WN4xjj%5NXKcG9

1h!U?F;sd>mRYw>q=ijq_Nqtz|F65^cW3nE^i#;wqXd8sGv2z>U$xLU1 zz=M9vh`e*#-sZ@MT)WraeQ$zH>wjn5kK#FXL0=qy-Z!7h_xPTaX1)D>LsL zX#Jt+2J1+huOuHlN2krWvu(I#{FmOyHbSi;L?iyBs9#e24gG6$?|=4%3&TW~8Xh-j zbXD(Y>F-|MXL-N(-kRQgoQ z@pSGQuqj6Q9B0cR_IdSx)tL?Kx5 zx9ngIZD`&;wL2J`5{ga0V>ipJZLrdzkIo~ z#JiLJJl}b{zr>p-#oJU|kVyvDaq(3(4Qcg8%OTygI{Gw8rAS WibJ>Y%EL<=mX3yjdaas6?0*3eFcupC literal 0 HcmV?d00001 diff --git a/src/images/mod/mod-warn.png b/src/images/mod/mod-warn.png new file mode 100644 index 0000000000000000000000000000000000000000..c72784bae5bdbff3aca14fd44bc05dfd6ef9bd3c GIT binary patch literal 3103 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 Date: Sun, 27 Nov 2022 22:01:01 -0600 Subject: [PATCH 02/26] purge-command Added purge.js command, which can either delete 2-99 messages or delete 100 messages from a user. --- src/commands/purge.js | 161 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/commands/purge.js diff --git a/src/commands/purge.js b/src/commands/purge.js new file mode 100644 index 0000000..a226877 --- /dev/null +++ b/src/commands/purge.js @@ -0,0 +1,161 @@ +const Discord = require('discord.js'); +const { SlashCommandBuilder } = require('@discordjs/builders'); +const util = require('../util'); + +/** + * + * @param {Discord.CommandInteraction} interaction + */ +async function purgeHandler(interaction) { + await interaction.deferReply({ + ephemeral: true + }); + + const guild = await interaction.guild.fetch(); + const executingMember = await interaction.member.fetch(); + const executor = executingMember.user; + const deleteAmount = interaction.options.getInteger('amount'); + const users = interaction.options.getString('users'); + + const eventLogEmbed = new Discord.MessageEmbed(); + + const purgeReplyEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/events/event-delete.png'); + if (users !== null) { + purgeReplyEmbed.setTitle('Messages Purged'); + purgeReplyEmbed.setColor(0xff6363); + purgeReplyEmbed.setThumbnail('attachment://event-delete.png'); + + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + + for (const userId of userIds) { + const member = await interaction.guild.members.fetch(userId); + const user = member.user; + + const userMessages = (await interaction.channel.messages.fetch()).filter( + (m) => m.author.id === member.id + ); + await interaction.channel.bulkDelete(userMessages); + + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Messages Purged_'); + eventLogEmbed.setDescription(`${user.username}'s messages deleted in ${interaction.channel.name}`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Channel', + value: `<#${interaction.channelId}>` + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-delete.png'); + + await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); + + purgeReplyEmbed.setDescription(`${user.username} has been successfully purged, here is where the messages been purged in`); + purgeReplyEmbed.addField('User Messages were purged in', `<#${interaction.channelId}>`, true); + purgeReplyEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + purgeReplyEmbed.setTimestamp(Date.now()); + await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); + } + } else { + if (isNaN(deleteAmount) || deleteAmount < 2) { + throw new Error('Please insert an amount between 2-99 to delete.'); + } + + if (parseInt(deleteAmount) > 99) { + throw new Error('Can only delete 2-99 messages at once. Please insert a lower number.'); + } else { + purgeReplyEmbed.setTitle('Messages Purged'); + purgeReplyEmbed.setColor(0xff6363); + purgeReplyEmbed.setThumbnail('attachment://event-delete.png'); + + // Bulk Delete + let { size } = await interaction.channel.bulkDelete(deleteAmount); + + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Messages Purged_'); + eventLogEmbed.setDescription(`${size} messages deleted in <#${interaction.channelId}>`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Channel', + value: `<#${interaction.channelId}>` + }, + { + name: 'Amount of Messages Deleted', + value: `${size}` + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://event-delete.png'); + + await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); + + purgeReplyEmbed.setDescription(`<#${interaction.channelId}> has been successfully purged, here is how many messages been purged`); + purgeReplyEmbed.addField('Amount of messages purged', `${size}`, true); + purgeReplyEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + purgeReplyEmbed.setTimestamp(Date.now()); + await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); + } + } +} + + const command = new SlashCommandBuilder() + .setDefaultPermission(false) + .setName('purge') + .setDescription('purge message(s)') + .addIntegerOption(option => { + return option.setName('amount') + .setDescription('Amount of messages deleted (2-99)') + .setRequired(false); + }) + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to purged (will only purge within the current channel)') + .setRequired(false); + }); + +module.exports = { + name: command.name, + handler: purgeHandler, + deploy: command.toJSON() +}; \ No newline at end of file From 43c75a8da3f60059acb8de0b594ae229e4cb882c Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Mon, 28 Nov 2022 21:47:58 -0600 Subject: [PATCH 03/26] pardon-and-misc Added pardon command, removed useless ??? else comment, replaced generic icon in purge, and other misc. fixes. --- README.md | 2 + src/commands/kick.js | 2 - src/commands/pardon.js | 259 ++++++++++++++++++++++++++++++++++ src/commands/purge.js | 47 +++--- src/commands/warn.js | 6 +- src/images/mod/mod-pardon.png | Bin 0 -> 4068 bytes src/images/mod/mod-purge.png | Bin 0 -> 5941 bytes 7 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 src/commands/pardon.js create mode 100644 src/images/mod/mod-pardon.png create mode 100644 src/images/mod/mod-purge.png diff --git a/README.md b/README.md index 9104534..09dc14b 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,6 @@ Features: - `/warn` command for warning users. Supports multiple users per warning. 3 warnings results in a kick. 4 warnings results in a ban - `/kick` command for kicking users. Supports multiple users per kick. 3 kicks results in a ban - `/ban` command for banning users. Supports multiple users per warning +- `/pardon` command for pardoning users (doesn't support unbans). Supports multiple users per pardon +- `/purge` command for purging messages. Can either delete 2-99 channel messages or user messages. Supports multiple users per user purge - NSFW content detection diff --git a/src/commands/kick.js b/src/commands/kick.js index c5b1edb..85e4757 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -164,8 +164,6 @@ async function kickHandler(interaction) { reason: reason, from_kick: true }); - } else { - // ??? } await Kicks.create({ diff --git a/src/commands/pardon.js b/src/commands/pardon.js new file mode 100644 index 0000000..779dae4 --- /dev/null +++ b/src/commands/pardon.js @@ -0,0 +1,259 @@ +const Discord = require('discord.js'); +const { SlashCommandBuilder } = require('@discordjs/builders'); +const Warnings = require('../models/warnings'); +const Kicks = require('../models/kicks'); +const Bans = require('../models/bans'); +const util = require('../util'); + +/** + * + * @param {Discord.CommandInteraction} interaction + */ +async function pardonHandler(interaction) { + await interaction.deferReply({ + ephemeral: true + }); + + const guild = await interaction.guild.fetch(); + const executingMember = await interaction.member.fetch(); + const executor = executingMember.user; + const type = interaction.options.getString('type'); + const users = interaction.options.getString('users'); + const reason = interaction.options.getString('reason'); + + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + + const image = new Discord.MessageAttachment('./src/images/mod/mod-pardon.png'); + // Initialize all of the embeds + const pardonedListEmbed = new Discord.MessageEmbed(); + const eventLogEmbed = new Discord.MessageEmbed(); + const pardonDmEmbed = new Discord.MessageEmbed(); + + for (const userId of userIds) { + const member = await interaction.guild.members.fetch(userId); + const user = member.user; + + if (type === 'warn_type') { // If chosen warn within options + let { userWarns } = await Warnings.findAndCountAll({ // Grab all warns + where: { + user_id: member.id + } + }); + if (userWarns < 1 || isNaN(userWarns)) { // User has no warns + throw new Error('This user has no warns, did you put in the wrong info?'); + } else { // User has warns + pardonedListEmbed.setTitle('User Pardoned'); + pardonedListEmbed.setColor(0xffffff); + pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their warns now`); + pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); + + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffffff); + eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); + eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setTitle('_Member Pardoned_'); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Warn Pardon' + }, + { + name: 'Reason', + value: reason + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); + + userWarns--; // Pardon the warn + + // Dm the user + pardonDmEmbed.setTitle('_Member Pardoned_'); + pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); + pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); + pardonDmEmbed.setColor(0xffffff); + pardonDmEmbed.setTimestamp(Date.now()); + pardonDmEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonDmEmbed.setFields( + { + name: 'Reason', + value: reason + }, + { + name: 'Total Warnings', + value: userWarns.toString() + }, + { + name: 'Warnings Left Until Kick', + value: Math.max(0, 3 - userWarns).toString() + }, + { + name: 'Warnings Left Until Ban', + value: Math.max(0, 4 - userWarns).toString() + } + ); + + await member.send({ + embeds: [pardonDmEmbed], + files: [image] + }).catch(() => console.log('Failed to DM user')); + + pardonedListEmbed.addField(`${member.user.username}'s warns`, userWarns.toString(), true); + pardonedListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonedListEmbed.setTimestamp(Date.now()); + } + } else if (type === 'kick_type') { + let { userKicks } = await Kicks.findAndCountAll({ // Grab all kicks + where: { + user_id: member.id + } + }); + if (userKicks < 1 || isNaN(userKicks)) { // User has no kicks + throw new Error('This user has no kicks, did you put in the wrong info?'); + } else { // User has kicks + pardonedListEmbed.setTitle('User Pardoned'); + pardonedListEmbed.setColor(0xffffff); + pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their kicks now`); + pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); + + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffffff); + eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); + eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setTitle('_Member Pardoned_'); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Kick Pardon' + }, + { + name: 'Reason', + value: reason + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); + + userKicks--; // Pardon the kick + + // Dm the user + pardonDmEmbed.setTitle('_Member Pardoned_'); + pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); + pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); + pardonDmEmbed.setColor(0xffffff); + pardonDmEmbed.setTimestamp(Date.now()); + pardonDmEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonDmEmbed.setFields( + { + name: 'Pardon Reason', + value: reason + }, + { + name: 'Amount Of Times Kicked Now', + value: userKicks.toString() + } + ); + + await member.send({ + embeds: [pardonDmEmbed], + files: [image] + }).catch(() => console.log('Failed to DM user')); + + pardonedListEmbed.addField(`${member.user.username}'s kicks`, userKicks.toString(), true); + pardonedListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonedListEmbed.setTimestamp(Date.now()); + } + } + } + await interaction.editReply({ embeds: [pardonedListEmbed], files: [image], ephemeral: true }); +} + +const command = new SlashCommandBuilder() + .setDefaultPermission(false) + .setName('pardon') + .setDescription('Pardon user(s)') + .addStringOption(option => { + return option.setName('type') + .setDescription('Type to pardon') + .setRequired(true) + .addChoices( + { name: 'Warn', value: 'warn_type'}, + { name: 'Kick', value: 'kick_type' } + ); + }) + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to pardon') + .setRequired(true); + }) + .addStringOption(option => { + return option.setName('reason') + .setDescription('Reason for the pardon') + .setRequired(true); + }); + +module.exports = { + name: command.name, + handler: pardonHandler, + deploy: command.toJSON() +}; \ No newline at end of file diff --git a/src/commands/purge.js b/src/commands/purge.js index a226877..f3c9dd2 100644 --- a/src/commands/purge.js +++ b/src/commands/purge.js @@ -20,11 +20,11 @@ async function purgeHandler(interaction) { const eventLogEmbed = new Discord.MessageEmbed(); const purgeReplyEmbed = new Discord.MessageEmbed(); - const image = new Discord.MessageAttachment('./src/images/events/event-delete.png'); - if (users !== null) { + const image = new Discord.MessageAttachment('./src/images/mod/mod-purge.png'); + if (users !== null) { // if a user(s) is actually given purgeReplyEmbed.setTitle('Messages Purged'); purgeReplyEmbed.setColor(0xff6363); - purgeReplyEmbed.setThumbnail('attachment://event-delete.png'); + purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; @@ -32,6 +32,7 @@ async function purgeHandler(interaction) { const member = await interaction.guild.members.fetch(userId); const user = member.user; + // Grab the user's messages const userMessages = (await interaction.channel.messages.fetch()).filter( (m) => m.author.id === member.id ); @@ -68,7 +69,7 @@ async function purgeHandler(interaction) { iconURL: guild.iconURL() }); eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setThumbnail('attachment://event-delete.png'); + eventLogEmbed.setThumbnail('attachment://mod-purge.png'); await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); @@ -82,16 +83,16 @@ async function purgeHandler(interaction) { await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); } } else { - if (isNaN(deleteAmount) || deleteAmount < 2) { + if (isNaN(deleteAmount) || deleteAmount < 2) { // Either nothing is given within the delete amount OR less than 2 throw new Error('Please insert an amount between 2-99 to delete.'); } - if (parseInt(deleteAmount) > 99) { + if (parseInt(deleteAmount) > 99) { // If the number is higher than 99 (discord's limit) throw new Error('Can only delete 2-99 messages at once. Please insert a lower number.'); - } else { + } else { // None of these are the case, continue to purge purgeReplyEmbed.setTitle('Messages Purged'); purgeReplyEmbed.setColor(0xff6363); - purgeReplyEmbed.setThumbnail('attachment://event-delete.png'); + purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); // Bulk Delete let { size } = await interaction.channel.bulkDelete(deleteAmount); @@ -123,7 +124,7 @@ async function purgeHandler(interaction) { iconURL: guild.iconURL() }); eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setThumbnail('attachment://event-delete.png'); + eventLogEmbed.setThumbnail('attachment://mod-purge.png'); await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); @@ -139,20 +140,20 @@ async function purgeHandler(interaction) { } } - const command = new SlashCommandBuilder() - .setDefaultPermission(false) - .setName('purge') - .setDescription('purge message(s)') - .addIntegerOption(option => { - return option.setName('amount') - .setDescription('Amount of messages deleted (2-99)') - .setRequired(false); - }) - .addStringOption(option => { - return option.setName('users') - .setDescription('User(s) to purged (will only purge within the current channel)') - .setRequired(false); - }); +const command = new SlashCommandBuilder() + .setDefaultPermission(false) + .setName('purge') + .setDescription('purge message(s)') + .addIntegerOption(option => { + return option.setName('amount') + .setDescription('Amount of messages deleted (2-99)') + .setRequired(false); + }) + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to purged (will only purge within the current channel)') + .setRequired(false); + }); module.exports = { name: command.name, diff --git a/src/commands/warn.js b/src/commands/warn.js index 891335a..19159dd 100644 --- a/src/commands/warn.js +++ b/src/commands/warn.js @@ -34,6 +34,10 @@ async function warnHandler(interaction) { const eventLogEmbed = new Discord.MessageEmbed(); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); eventLogEmbed.setColor(0xffc800); eventLogEmbed.setDescription(`${user.username} has been warned in Pretendo by ${executor.username}`); image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); @@ -175,8 +179,6 @@ async function warnHandler(interaction) { reason: reason, from_warning: true }); - } else { - // ??? } } else { punishmentEmbed = new Discord.MessageEmbed(); diff --git a/src/images/mod/mod-pardon.png b/src/images/mod/mod-pardon.png new file mode 100644 index 0000000000000000000000000000000000000000..91e17b7513e9da78188e83da29be486009d1e9cd GIT binary patch literal 4068 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 G7yk(wM?6CS literal 0 HcmV?d00001 From 7f0bbe6b4f6e6b55ac9e85f3ff2bc5d3c5f5c0dc Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:52:14 -0600 Subject: [PATCH 04/26] log-unbans-and-consistency Logs unbans and now follows the correct naming convention. --- src/bot.js | 7 +- src/events/guildBanRemove.js | 67 +++++++++++++++++++ ...{guildMemberAdded.js => guildMemberAdd.js} | 4 +- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/events/guildBanRemove.js rename src/events/{guildMemberAdded.js => guildMemberAdd.js} (92%) diff --git a/src/bot.js b/src/bot.js index 6dd5217..d7fb889 100644 --- a/src/bot.js +++ b/src/bot.js @@ -1,7 +1,8 @@ const Discord = require('discord.js'); const guildMemberRemoveHandler = require('./events/guildMemberRemove'); const guildMemberUpdateHandler = require('./events/guildMemberUpdate'); -const guildMemberAddedHandler = require('./events/guildMemberAdded'); +const guildMemberAddHandler = require('./events/guildMemberAdd'); +const guildBanRemoveHandler = require('./events/guildBanRemove'); const interactionCreateHandler = require('./events/interactionCreate'); const messageCreateHandler = require('./events/messageCreate'); const messageDeleteHandler = require('./events/messageDelete'); @@ -14,6 +15,7 @@ const client = new Discord.Client({ Discord.Intents.FLAGS.GUILDS, Discord.Intents.FLAGS.GUILD_MESSAGES, Discord.Intents.FLAGS.GUILD_MEMBERS, + Discord.Intents.FLAGS.GUILD_BANS, ] }); @@ -22,7 +24,8 @@ client.commands = new Discord.Collection(); client.on('ready', readyHandler); client.on('guildMemberRemove', guildMemberRemoveHandler); client.on('guildMemberUpdate', guildMemberUpdateHandler); -client.on('guildMemberAdd', guildMemberAddedHandler) +client.on('guildMemberAdd', guildMemberAddHandler); +client.on('guildBanRemove', guildBanRemoveHandler); client.on('interactionCreate', interactionCreateHandler); client.on('messageCreate', messageCreateHandler); client.on('messageDelete', messageDeleteHandler); diff --git a/src/events/guildBanRemove.js b/src/events/guildBanRemove.js new file mode 100644 index 0000000..6028da8 --- /dev/null +++ b/src/events/guildBanRemove.js @@ -0,0 +1,67 @@ +const Discord = require('discord.js'); +const util = require('../util'); + +/** + * + * @param {Discord.GuildBan} ban + */ + async function guildBanRemoveHandler(ban) { + const guild = ban.guild; + const user = ban.user; + + const auditLogs = await guild.fetchAuditLogs({ + limit: 1, + type: 'MEMBER_BAN_REMOVE' + }); + + const latestLog = auditLogs.entries.first(); + + const { executor } = latestLog; + + const eventLogEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/mod/mod-pardon.png'); + + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffffff); + eventLogEmbed.setTitle('_Member Pardoned_'); + eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Ban Pardon' + }, + { + name: 'From Bot', + value: 'false' + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); + + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); +} +module.exports = guildBanRemoveHandler; \ No newline at end of file diff --git a/src/events/guildMemberAdded.js b/src/events/guildMemberAdd.js similarity index 92% rename from src/events/guildMemberAdded.js rename to src/events/guildMemberAdd.js index ca0fb9f..977d13c 100644 --- a/src/events/guildMemberAdded.js +++ b/src/events/guildMemberAdd.js @@ -5,7 +5,7 @@ const util = require('../util'); * * @param {Discord.GuildMember} member */ -async function guildMemberAddedHandler(member) { +async function guildMemberAddHandler(member) { const guild = member.guild; const user = member.user; @@ -43,4 +43,4 @@ async function guildMemberAddedHandler(member) { await util.sendEventLogMessage('channels.event-logs', guild, null, eventLogEmbed, image, null); } -module.exports = guildMemberAddedHandler; \ No newline at end of file +module.exports = guildMemberAddHandler; \ No newline at end of file From 4a35b85b999b196a53c0496adf92826601a7e207 Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Thu, 1 Dec 2022 17:51:33 -0600 Subject: [PATCH 05/26] bugfixes Fixed the major issue within this PR with nsfw-check.js, and fixed an error within guildMemberUpdate.js, which would crash Chubby if a timedout was revoked. --- src/events/guildMemberUpdate.js | 2 +- src/events/messageDelete.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/events/guildMemberUpdate.js b/src/events/guildMemberUpdate.js index 4ef119c..429f8cb 100644 --- a/src/events/guildMemberUpdate.js +++ b/src/events/guildMemberUpdate.js @@ -29,7 +29,7 @@ async function guildMemberUpdateHandler(oldMember, newMember) { iconURL: guild.iconURL() }); - if (oldMember.communicationDisabledUntilTimestamp !== newMember.communicationDisabledUntilTimestamp) { + if (oldMember.communicationDisabledUntilTimestamp < newMember.communicationDisabledUntilTimestamp) { const image = new Discord.MessageAttachment('./src/images/events/event-timedout.png'); eventLogEmbed.setAuthor({ name: user.tag, diff --git a/src/events/messageDelete.js b/src/events/messageDelete.js index 4cbe9c7..80c52f4 100644 --- a/src/events/messageDelete.js +++ b/src/events/messageDelete.js @@ -12,6 +12,18 @@ async function messageDeleteHandler(message) { const member = await message.member.fetch(); const user = member.user; + const auditLogs = await guild.fetchAuditLogs({ + limit: 1, + type: 'MESSAGE_DELETE' + }); + + const latestLog = auditLogs.entries.first(); + + const { executor } = latestLog; + + if (executor.id = guild.me.id) return; + + const messageContent = message.content.length > 1024 ? message.content.substr(0, 1023) + '…' : message.content; const eventLogEmbed = new Discord.MessageEmbed(); From 6133180f00a99f2acc23e5743bdfea70882cf091 Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Thu, 1 Dec 2022 19:41:25 -0600 Subject: [PATCH 06/26] Joke Regexes Adds joke regexes, which will reply (within the select channel) with a certain prompt. --- src/events/messageCreate.js | 43 ++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 8523ee9..7ad5805 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -2,12 +2,17 @@ const Discord = require('discord.js'); const checkNSFW = require('../check-nsfw'); const urlRegex = /(https?:\/\/[^\s]+)/g; +const reduxRegex = /\bredux{1,}\b/gi; +const rawrRegex = /\brawr{1,}\b/gi; +const fourRegex = /\b2014{1,}\b/gi; +const linuxRegex = /\blinux{1,}\b/gi; +const chubbyRegex = /\bchubby{1,}\b/gi; /** * * @param {Discord.Message} message */ -function messageHandler(message) { +async function messageHandler(message) { // Ignore bot messages if (message.author.bot) return; @@ -25,6 +30,42 @@ function messageHandler(message) { checkNSFW(message, urls); } + + // Joke Regexes, ignore outside of any aditional jokes. + let jokeRegex; + + if (reduxRegex.test(message.content)){ + jokeRegex = 'redux'; + } else if (rawrRegex.test(message.content)) { + jokeRegex = 'rawr'; + } else if (fourRegex.test(message.content)) { + jokeRegex = 'four'; + } else if (linuxRegex.test(message.content)) { + jokeRegex = 'linux'; + } else if (chubbyRegex.test(message.content)) { + jokeRegex = 'chubby'; + } + + if (message.channelId === 'Insert Bot Spam Channel ID here!' || message.channelId === '415616380570697729') { // Holds both your testing channel id and the Pretendo Botspam channel id + switch (jokeRegex) { + case 'redux': + await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); + break; + case 'rawr': + await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); + break; + case 'four': + await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); + break; + case 'linux': + await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); + break; + case 'chubby': + await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); + break; + default: + break; + } } module.exports = messageHandler; \ No newline at end of file From 24e2ccd3664f1d5eb32ba73908aed8fc9ad567f5 Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Thu, 1 Dec 2022 20:43:11 -0600 Subject: [PATCH 07/26] Fix switch function Adds the missing bracket of the switch function --- src/events/messageCreate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 7ad5805..7a4ce6d 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -66,6 +66,7 @@ async function messageHandler(message) { default: break; } + } } module.exports = messageHandler; \ No newline at end of file From 29db884125bd694adb108052067474dfd3d7594f Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Thu, 1 Dec 2022 20:51:54 -0600 Subject: [PATCH 08/26] Fixed broken regex Fixes problem where it'd ping the bot itself instead of the user, and removed useless switch function. --- src/events/messageCreate.js | 38 ++++++++----------------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 7a4ce6d..bba1dd3 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,7 +1,8 @@ const Discord = require('discord.js'); - +// NSFW Vars const checkNSFW = require('../check-nsfw'); const urlRegex = /(https?:\/\/[^\s]+)/g; +// Misc. Vars const reduxRegex = /\bredux{1,}\b/gi; const rawrRegex = /\brawr{1,}\b/gi; const fourRegex = /\b2014{1,}\b/gi; @@ -32,40 +33,17 @@ async function messageHandler(message) { } // Joke Regexes, ignore outside of any aditional jokes. - let jokeRegex; - + if (message.channelId === 'Insert Your Bot Spam Channel ID Here' || message.channelId === '415616380570697729') if (reduxRegex.test(message.content)){ - jokeRegex = 'redux'; + await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); } else if (rawrRegex.test(message.content)) { - jokeRegex = 'rawr'; + await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); } else if (fourRegex.test(message.content)) { - jokeRegex = 'four'; + await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); } else if (linuxRegex.test(message.content)) { - jokeRegex = 'linux'; + await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); } else if (chubbyRegex.test(message.content)) { - jokeRegex = 'chubby'; - } - - if (message.channelId === 'Insert Bot Spam Channel ID here!' || message.channelId === '415616380570697729') { // Holds both your testing channel id and the Pretendo Botspam channel id - switch (jokeRegex) { - case 'redux': - await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); - break; - case 'rawr': - await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); - break; - case 'four': - await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); - break; - case 'linux': - await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); - break; - case 'chubby': - await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); - break; - default: - break; - } + await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); } } From 3f14f4c102e00a683418f0858b2279ec85b01b97 Mon Sep 17 00:00:00 2001 From: Marisa Gaming <111249711+CirnoMoment@users.noreply.github.com> Date: Fri, 2 Dec 2022 15:55:11 -0600 Subject: [PATCH 09/26] Minor fixes Some minor bugfixes, just to wrap the joke regexes perfectly. --- example.config.json | 1 + src/events/messageCreate.js | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/example.config.json b/example.config.json index 0ee28c2..32ef153 100644 --- a/example.config.json +++ b/example.config.json @@ -1,5 +1,6 @@ { "bot_token": "TOKEN", + "botspam_id": "CHANNEL_ID", "quantized_nsfw_model": false, "sequelize": { "force": false, diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index bba1dd3..b38213b 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,7 +1,9 @@ const Discord = require('discord.js'); +const config = require('../../config.json'); // NSFW Vars const checkNSFW = require('../check-nsfw'); const urlRegex = /(https?:\/\/[^\s]+)/g; + // Misc. Vars const reduxRegex = /\bredux{1,}\b/gi; const rawrRegex = /\brawr{1,}\b/gi; @@ -33,17 +35,18 @@ async function messageHandler(message) { } // Joke Regexes, ignore outside of any aditional jokes. - if (message.channelId === 'Insert Your Bot Spam Channel ID Here' || message.channelId === '415616380570697729') - if (reduxRegex.test(message.content)){ - await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); - } else if (rawrRegex.test(message.content)) { - await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); - } else if (fourRegex.test(message.content)) { - await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); - } else if (linuxRegex.test(message.content)) { - await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); - } else if (chubbyRegex.test(message.content)) { - await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); + if (message.channelId === config.botspam_id) { + if (reduxRegex.test(message.content)){ + await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); + } else if (rawrRegex.test(message.content)) { + await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); + } else if (fourRegex.test(message.content)) { + await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); + } else if (linuxRegex.test(message.content)) { + await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); + } else if (chubbyRegex.test(message.content)) { + await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); + } } } From 637cc2c5c51c4ad9f628e7e5746bcd1d7b321a9d Mon Sep 17 00:00:00 2001 From: Marisa Gaming Date: Fri, 16 Dec 2022 16:10:00 -0600 Subject: [PATCH 10/26] Fixes critical issue Makes sure to check hierarchy before banning/kicking/warning, also fixes crashing in the case no reasoning is given when manually banning/kicking. --- src/commands/ban.js | 7 +++++++ src/commands/kick.js | 7 +++++++ src/commands/warn.js | 7 +++++++ src/events/guildMemberRemove.js | 5 +++++ 4 files changed, 26 insertions(+) diff --git a/src/commands/ban.js b/src/commands/ban.js index c9db5ab..c74f30d 100644 --- a/src/commands/ban.js +++ b/src/commands/ban.js @@ -30,6 +30,13 @@ async function banHandler(interaction) { const member = await interaction.guild.members.fetch(userId); const user = member.user; + // Checks if they're above/equal to the executor + if (member.roles.highest.position >= executingMember.roles.highest.position) { + await interaction.editReply(`You cannot ban ${member.username} as they have a higher role compared to you.`); + return; + } + + // Creates mod logs const eventLogEmbed = new Discord.MessageEmbed(); eventLogEmbed.setAuthor({ diff --git a/src/commands/kick.js b/src/commands/kick.js index 85e4757..5058fed 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -31,6 +31,13 @@ async function kickHandler(interaction) { const member = await interaction.guild.members.fetch(userId); const user = member.user; + // Checks if they're above/equal to the executor + if (member.roles.highest.position >= executingMember.roles.highest.position) { + await interaction.editReply(`You cannot kick ${member.username} as they have a higher role compared to you.`); + return; + } + + // Creates mod logs const eventLogEmbed = new Discord.MessageEmbed(); eventLogEmbed.setAuthor({ diff --git a/src/commands/warn.js b/src/commands/warn.js index 19159dd..239f851 100644 --- a/src/commands/warn.js +++ b/src/commands/warn.js @@ -32,6 +32,13 @@ async function warnHandler(interaction) { const member = await interaction.guild.members.fetch(userId); const user = member.user; + // Checks if they're above/equal to the executor + if (member.roles.highest.position >= executingMember.roles.highest.position) { + await interaction.editReply(`You cannot warn ${member.username} as they have a higher role compared to you.`); + return; + } + + // Creates mod logs const eventLogEmbed = new Discord.MessageEmbed(); eventLogEmbed.setAuthor({ diff --git a/src/events/guildMemberRemove.js b/src/events/guildMemberRemove.js index 94f47dc..f1fe045 100644 --- a/src/events/guildMemberRemove.js +++ b/src/events/guildMemberRemove.js @@ -84,6 +84,11 @@ async function guildMemberRemoveHandler(member) { eventLogEmbed.setThumbnail('attachment://mod-ban.png'); } + // Incase of the reason being not filled + if (latestLog.reason === null) { + latestLog.reason = "*No Reason Given*"; + } + eventLogEmbed.setFields( { name: 'User', From afc8adbcaef8eb45b22edf426f1c26c9b9787673 Mon Sep 17 00:00:00 2001 From: Marisa Gaming Date: Sat, 17 Dec 2022 19:21:41 -0600 Subject: [PATCH 11/26] Replaces settings-which Replaces settings-which to discord options, similarly to Bandwidth. --- src/commands/settings.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/commands/settings.js b/src/commands/settings.js index d2332b9..773b9c3 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -52,19 +52,6 @@ async function settingsHandler(interaction) { return; } - if (interaction.options.getSubcommand() === 'which') { - await interaction.reply({ - content: `**possible settings**:\n${editableOptions - .map((v) => `\`${v}\``) - .join('\n')}`, - ephemeral: true, - allowedMentions: { - parse: [], // dont allow tagging anything - }, - }); - return; - } - throw new Error('unhandled subcommand'); } @@ -80,6 +67,11 @@ command.addSubcommand((cmd) => { option.setName('key'); option.setDescription('Key to modify'); option.setRequired(true); + for(setting in editableOptions) { + option.addChoices( + { name: editableOptions[setting], value: editableOptions[setting] } + ); + } return option; }); cmd.addStringOption((option) => { @@ -97,15 +89,15 @@ command.addSubcommand((cmd) => { option.setName('key'); option.setDescription('Key to modify'); option.setRequired(true); + for(setting in editableOptions) { + option.addChoices( + { name: editableOptions[setting], value: editableOptions[setting] } + ); + } return option; }); return cmd; }); -command.addSubcommand((cmd) => { - cmd.setName('which'); - cmd.setDescription('which settings are valid?'); - return cmd; -}); module.exports = { name: command.name, From 51419ab9e344dfe6eeea398bad01bd1058235e7a Mon Sep 17 00:00:00 2001 From: Marisa Gaming Date: Tue, 27 Dec 2022 14:15:24 -0600 Subject: [PATCH 12/26] Added Warnings Fixed a lil bug where it would say "Undefined cannot be warned". Also added Eyad's warnings suggestion. --- src/commands/ban.js | 2 +- src/commands/kick.js | 2 +- src/commands/warn.js | 2 +- src/commands/warnings.js | 77 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/commands/warnings.js diff --git a/src/commands/ban.js b/src/commands/ban.js index c74f30d..79b111e 100644 --- a/src/commands/ban.js +++ b/src/commands/ban.js @@ -32,7 +32,7 @@ async function banHandler(interaction) { // Checks if they're above/equal to the executor if (member.roles.highest.position >= executingMember.roles.highest.position) { - await interaction.editReply(`You cannot ban ${member.username} as they have a higher role compared to you.`); + await interaction.editReply(`You cannot ban ${user.username} as they have a higher role compared to you.`); return; } diff --git a/src/commands/kick.js b/src/commands/kick.js index 5058fed..f71104d 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -33,7 +33,7 @@ async function kickHandler(interaction) { // Checks if they're above/equal to the executor if (member.roles.highest.position >= executingMember.roles.highest.position) { - await interaction.editReply(`You cannot kick ${member.username} as they have a higher role compared to you.`); + await interaction.editReply(`You cannot kick ${user.username} as they have a higher role compared to you.`); return; } diff --git a/src/commands/warn.js b/src/commands/warn.js index 239f851..36177c5 100644 --- a/src/commands/warn.js +++ b/src/commands/warn.js @@ -34,7 +34,7 @@ async function warnHandler(interaction) { // Checks if they're above/equal to the executor if (member.roles.highest.position >= executingMember.roles.highest.position) { - await interaction.editReply(`You cannot warn ${member.username} as they have a higher role compared to you.`); + await interaction.editReply(`You cannot warn ${user.username} as they have a higher role compared to you.`); return; } diff --git a/src/commands/warnings.js b/src/commands/warnings.js new file mode 100644 index 0000000..088dab3 --- /dev/null +++ b/src/commands/warnings.js @@ -0,0 +1,77 @@ +const Discord = require('discord.js'); +const { SlashCommandBuilder } = require('@discordjs/builders'); +const Warnings = require('../models/warnings'); + +/** + * + * @param {Discord.CommandInteraction} interaction + */ +async function warningsHandler(interaction) { + await interaction.deferReply({ + ephemeral: true + }); + + const guild = await interaction.guild.fetch(); + const users = interaction.options.getString('users'); + + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + + const warningListEmbed = new Discord.MessageEmbed(); + warningListEmbed.setTitle('Previous User Warnings'); + warningListEmbed.setColor(0xffc800); + + + for (const userId of userIds) { + const member = await interaction.guild.members.fetch(userId); + const user = member.user; + + const { count } = await Warnings.findAndCountAll({ + where: { + user_id: member.id + } + }); + + warningListEmbed.setDescription(`${user.username} previous warns:`) + warningListEmbed.addFields( + { + name: `${user.username}'s warns`, + value: count.toString() + }, + { + name: 'Warnings Left Until Kick', + value: Math.max(0, 3 - count).toString() + }, + { + name: 'Warnings Left Until Ban', + value: Math.max(0, 4 - count).toString() + } + ); + + warningListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + + warningListEmbed.setTimestamp(Date.now()); + } + + const image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); + warningListEmbed.setThumbnail('attachment://mod-warn.png'); + await interaction.editReply({ embeds: [warningListEmbed], files: [image], ephemeral: true }); +} + +const command = new SlashCommandBuilder() + .setDefaultPermission(false) + .setName('warnings') + .setDescription("See user(s) warns") + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to see warns of') + .setRequired(true); + }); + +module.exports = { + name: command.name, + handler: warningsHandler, + deploy: command.toJSON() +}; \ No newline at end of file From 791a73f619c5e1dbff5cf4f08089204a3ca6a091 Mon Sep 17 00:00:00 2001 From: Director Reiuji <111249711+DirectorReiuji@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:44:49 -0600 Subject: [PATCH 13/26] Fixed issues (minus spacing) --- README.md | 2 +- example.config.json | 1 - src/commands/ban.js | 7 +-- src/commands/kick.js | 2 +- src/commands/pardon.js | 6 ++- src/commands/purge.js | 91 +++++++++++++++++------------------- src/commands/settings.js | 4 ++ src/events/guildMemberAdd.js | 2 +- src/events/messageCreate.js | 23 --------- src/events/messageDelete.js | 2 +- src/util.js | 4 -- 11 files changed, 60 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 09dc14b..bdf1306 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,6 @@ Features: - `/warn` command for warning users. Supports multiple users per warning. 3 warnings results in a kick. 4 warnings results in a ban - `/kick` command for kicking users. Supports multiple users per kick. 3 kicks results in a ban - `/ban` command for banning users. Supports multiple users per warning -- `/pardon` command for pardoning users (doesn't support unbans). Supports multiple users per pardon +- `/pardon` command for pardoning warns and kicks from users. Supports multiple users per pardon - `/purge` command for purging messages. Can either delete 2-99 channel messages or user messages. Supports multiple users per user purge - NSFW content detection diff --git a/example.config.json b/example.config.json index 32ef153..0ee28c2 100644 --- a/example.config.json +++ b/example.config.json @@ -1,6 +1,5 @@ { "bot_token": "TOKEN", - "botspam_id": "CHANNEL_ID", "quantized_nsfw_model": false, "sequelize": { "force": false, diff --git a/src/commands/ban.js b/src/commands/ban.js index 79b111e..bb387c7 100644 --- a/src/commands/ban.js +++ b/src/commands/ban.js @@ -23,9 +23,10 @@ async function banHandler(interaction) { const bansListEmbed = new Discord.MessageEmbed(); bansListEmbed.setTitle('User Bans'); bansListEmbed.setColor(0xa30000); - const image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); bansListEmbed.setThumbnail('attachment://mod-ban.png'); + const image = new Discord.MessageAttachment('./src/images/mod/mod-ban.png'); + for (const userId of userIds) { const member = await interaction.guild.members.fetch(userId); const user = member.user; @@ -44,7 +45,7 @@ async function banHandler(interaction) { iconURL: user.avatarURL() }); eventLogEmbed.setColor(0xa30000); - eventLogEmbed.setDescription(`${user.username} has been banned from Pretendo by ${executor.username}`); + eventLogEmbed.setDescription(`${user.username} has been banned by ${executor.username}`); eventLogEmbed.setTimestamp(Date.now()); eventLogEmbed.setTitle('_Member Banned_'); eventLogEmbed.setFields( @@ -122,7 +123,7 @@ async function banHandler(interaction) { reason: reason }); - bansListEmbed.setDescription(`${user.username} has been successfully banned, here is their previous bans`) + bansListEmbed.setDescription(`${user.username} has been successfully banned`) bansListEmbed.addField(`${member.user.username}'s bans`, (count + 1).toString(), true); bansListEmbed.setFooter({ text: 'Pretendo Network', diff --git a/src/commands/kick.js b/src/commands/kick.js index f71104d..f806735 100644 --- a/src/commands/kick.js +++ b/src/commands/kick.js @@ -179,7 +179,7 @@ async function kickHandler(interaction) { reason: reason }); - kicksListEmbed.setDescription(`${user.username} has been successfully kicked, here is their previous kicks`) + kicksListEmbed.setDescription(`${user.username} has been successfully kicked`) kicksListEmbed.addField(`${member.user.username}'s kicks`, (count + 1).toString(), true); kicksListEmbed.setFooter({ text: 'Pretendo Network', diff --git a/src/commands/pardon.js b/src/commands/pardon.js index 779dae4..e24dc5f 100644 --- a/src/commands/pardon.js +++ b/src/commands/pardon.js @@ -39,8 +39,9 @@ async function pardonHandler(interaction) { user_id: member.id } }); + if (userWarns < 1 || isNaN(userWarns)) { // User has no warns - throw new Error('This user has no warns, did you put in the wrong info?'); + throw new Error('This user has no warnings. Ensure you are using the correct user'); } else { // User has warns pardonedListEmbed.setTitle('User Pardoned'); pardonedListEmbed.setColor(0xffffff); @@ -138,8 +139,9 @@ async function pardonHandler(interaction) { user_id: member.id } }); + if (userKicks < 1 || isNaN(userKicks)) { // User has no kicks - throw new Error('This user has no kicks, did you put in the wrong info?'); + throw new Error('This user has no kicks. Ensure you are using the correct user.'); } else { // User has kicks pardonedListEmbed.setTitle('User Pardoned'); pardonedListEmbed.setColor(0xffffff); diff --git a/src/commands/purge.js b/src/commands/purge.js index f3c9dd2..a88b63b 100644 --- a/src/commands/purge.js +++ b/src/commands/purge.js @@ -21,6 +21,7 @@ async function purgeHandler(interaction) { const purgeReplyEmbed = new Discord.MessageEmbed(); const image = new Discord.MessageAttachment('./src/images/mod/mod-purge.png'); + if (users !== null) { // if a user(s) is actually given purgeReplyEmbed.setTitle('Messages Purged'); purgeReplyEmbed.setColor(0xff6363); @@ -83,60 +84,56 @@ async function purgeHandler(interaction) { await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); } } else { - if (isNaN(deleteAmount) || deleteAmount < 2) { // Either nothing is given within the delete amount OR less than 2 + if (isNaN(deleteAmount) || deleteAmount < 2 || parseInt(deleteAmount) > 99) { // Either nothing is given within the delete amount OR less than 2 throw new Error('Please insert an amount between 2-99 to delete.'); } - - if (parseInt(deleteAmount) > 99) { // If the number is higher than 99 (discord's limit) - throw new Error('Can only delete 2-99 messages at once. Please insert a lower number.'); - } else { // None of these are the case, continue to purge - purgeReplyEmbed.setTitle('Messages Purged'); - purgeReplyEmbed.setColor(0xff6363); - purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); + + purgeReplyEmbed.setTitle('Messages Purged'); + purgeReplyEmbed.setColor(0xff6363); + purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); - // Bulk Delete - let { size } = await interaction.channel.bulkDelete(deleteAmount); + // Bulk Delete + let { size } = await interaction.channel.bulkDelete(deleteAmount); - eventLogEmbed.setColor(0xff6363); - eventLogEmbed.setTitle('_Messages Purged_'); - eventLogEmbed.setDescription(`${size} messages deleted in <#${interaction.channelId}>`); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setFields( - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Channel', + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Messages Purged_'); + eventLogEmbed.setDescription(`${size} messages deleted in <#${interaction.channelId}>`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Channel', value: `<#${interaction.channelId}>` - }, - { - name: 'Amount of Messages Deleted', - value: `${size}` - } - ); - eventLogEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setThumbnail('attachment://mod-purge.png'); + }, + { + name: 'Amount of Messages Deleted', + value: `${size}` + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://mod-purge.png'); - await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); + await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); - purgeReplyEmbed.setDescription(`<#${interaction.channelId}> has been successfully purged, here is how many messages been purged`); - purgeReplyEmbed.addField('Amount of messages purged', `${size}`, true); - purgeReplyEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - purgeReplyEmbed.setTimestamp(Date.now()); - await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); - } + purgeReplyEmbed.setDescription(`<#${interaction.channelId}> has been successfully purged, here is how many messages been purged`); + purgeReplyEmbed.addField('Amount of messages purged', `${size}`, true); + purgeReplyEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + purgeReplyEmbed.setTimestamp(Date.now()); + await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); } } diff --git a/src/commands/settings.js b/src/commands/settings.js index 773b9c3..4f437fc 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -67,11 +67,13 @@ command.addSubcommand((cmd) => { option.setName('key'); option.setDescription('Key to modify'); option.setRequired(true); + for(setting in editableOptions) { option.addChoices( { name: editableOptions[setting], value: editableOptions[setting] } ); } + return option; }); cmd.addStringOption((option) => { @@ -89,11 +91,13 @@ command.addSubcommand((cmd) => { option.setName('key'); option.setDescription('Key to modify'); option.setRequired(true); + for(setting in editableOptions) { option.addChoices( { name: editableOptions[setting], value: editableOptions[setting] } ); } + return option; }); return cmd; diff --git a/src/events/guildMemberAdd.js b/src/events/guildMemberAdd.js index 977d13c..ed7a659 100644 --- a/src/events/guildMemberAdd.js +++ b/src/events/guildMemberAdd.js @@ -9,7 +9,7 @@ async function guildMemberAddHandler(member) { const guild = member.guild; const user = member.user; - const eventLogEmbed = new Discord.MessageEmbed(); + const eventLogEmbed = new Discord.MessageEmbed(); const image = new Discord.MessageAttachment('./src/images/events/event-join.png'); eventLogEmbed.setAuthor({ diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index b38213b..7d412d1 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,16 +1,8 @@ const Discord = require('discord.js'); -const config = require('../../config.json'); // NSFW Vars const checkNSFW = require('../check-nsfw'); const urlRegex = /(https?:\/\/[^\s]+)/g; -// Misc. Vars -const reduxRegex = /\bredux{1,}\b/gi; -const rawrRegex = /\brawr{1,}\b/gi; -const fourRegex = /\b2014{1,}\b/gi; -const linuxRegex = /\blinux{1,}\b/gi; -const chubbyRegex = /\bchubby{1,}\b/gi; - /** * * @param {Discord.Message} message @@ -33,21 +25,6 @@ async function messageHandler(message) { checkNSFW(message, urls); } - - // Joke Regexes, ignore outside of any aditional jokes. - if (message.channelId === config.botspam_id) { - if (reduxRegex.test(message.content)){ - await message.reply(`Yo <@${message.author.id}> it's been 3 years...\n\nStill not upload? Come on man I enjoy your vids so much the intro always brightened my day, make 1 last vid and if it doesn't get 100 likes you can quit.`); - } else if (rawrRegex.test(message.content)) { - await message.reply(`what about we convert ${message.guild} into full time furry memes and rp? game servers are overrated`); - } else if (fourRegex.test(message.content)) { - await message.reply('in the year 2014 the world ceased to exist for exactly 365 days until that time passed all of the memories you felt in the year 2014 never happened its all an injection of thoughts into your brain which is pretty intresting if you think about it, the fact that we have after that whole 2014 thing went into our brains the world never really ran at full speed again instead we just ran at 0.84638267x speed which is hard for us to notice in day to day use but overtime its really easy to measure with a tape measure to see that it is longer than the average amount of time to do anything Its honestly crazy how no one else noticed except for me after i drank 12 12 packs of monster at 4:14 AM EST on Thanksgiving which is not too far away from November 30th'); - } else if (linuxRegex.test(message.content)) { - await message.reply(`I'd just like to interject for a moment, <@${message.author.id}>. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called "Linux", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called "Linux" distributions are really distributions of GNU/Linux.`); - } else if (chubbyRegex.test(message.content)) { - await message.reply(`I'm Chubby the Snowman, and I've been stuck here ever since Nintendo forgot me from the days of yore. Please, <@${message.author.id}>, help me get back to Nintendo so I can finally get my deserved sequel, which is actually rushed and has several game breaking bugs, even crashing on release!`); - } - } } module.exports = messageHandler; \ No newline at end of file diff --git a/src/events/messageDelete.js b/src/events/messageDelete.js index 80c52f4..4fdab2a 100644 --- a/src/events/messageDelete.js +++ b/src/events/messageDelete.js @@ -21,7 +21,7 @@ async function messageDeleteHandler(message) { const { executor } = latestLog; - if (executor.id = guild.me.id) return; + if (executor.id == guild.me.id) return; const messageContent = message.content.length > 1024 ? message.content.substr(0, 1023) + '…' : message.content; diff --git a/src/util.js b/src/util.js index 20dbe55..e4ea17e 100644 --- a/src/util.js +++ b/src/util.js @@ -35,11 +35,7 @@ async function sendEventLogMessage(logType, guild, originId, embed, file, compon if (!logChannel) { console.log('Missing log channel!'); } else { - if (components === null) { - await logChannel.send({ embeds: [embed], files: [file] }); - } else { await logChannel.send({ embeds: [embed], files: [file], components: [components] }); - } } } From 5ec875cbedf7b867a95defab7dca0cbc824f2f1d Mon Sep 17 00:00:00 2001 From: Director Reiuji <111249711+DirectorReiuji@users.noreply.github.com> Date: Wed, 6 Mar 2024 18:52:24 -0600 Subject: [PATCH 14/26] Added fixes and fixed spacing --- src/commands/pardon.js | 443 ++++++++++++++++++----------------- src/commands/purge.js | 260 ++++++++++---------- src/commands/warnings.js | 66 +++--- src/events/guildBanRemove.js | 58 ++--- src/events/messageDelete.js | 12 - src/util.js | 5 + 6 files changed, 430 insertions(+), 414 deletions(-) diff --git a/src/commands/pardon.js b/src/commands/pardon.js index e24dc5f..7b88a05 100644 --- a/src/commands/pardon.js +++ b/src/commands/pardon.js @@ -4,251 +4,274 @@ const Warnings = require('../models/warnings'); const Kicks = require('../models/kicks'); const Bans = require('../models/bans'); const util = require('../util'); +const warn = require('./warn'); /** * * @param {Discord.CommandInteraction} interaction */ async function pardonHandler(interaction) { - await interaction.deferReply({ + await interaction.deferReply({ ephemeral: true }); const guild = await interaction.guild.fetch(); const executingMember = await interaction.member.fetch(); const executor = executingMember.user; - const type = interaction.options.getString('type'); + const type = interaction.options.getString('type'); const users = interaction.options.getString('users'); - const reason = interaction.options.getString('reason'); + const reason = interaction.options.getString('reason'); - const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; - const image = new Discord.MessageAttachment('./src/images/mod/mod-pardon.png'); - // Initialize all of the embeds - const pardonedListEmbed = new Discord.MessageEmbed(); - const eventLogEmbed = new Discord.MessageEmbed(); - const pardonDmEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/mod/mod-pardon.png'); + // Initialize all of the embeds + const pardonedListEmbed = new Discord.MessageEmbed(); + const eventLogEmbed = new Discord.MessageEmbed(); + const pardonDmEmbed = new Discord.MessageEmbed(); for (const userId of userIds) { - const member = await interaction.guild.members.fetch(userId); + const member = await interaction.guild.members.fetch(userId); const user = member.user; - if (type === 'warn_type') { // If chosen warn within options - let { userWarns } = await Warnings.findAndCountAll({ // Grab all warns - where: { - user_id: member.id - } - }); - - if (userWarns < 1 || isNaN(userWarns)) { // User has no warns - throw new Error('This user has no warnings. Ensure you are using the correct user'); - } else { // User has warns - pardonedListEmbed.setTitle('User Pardoned'); - pardonedListEmbed.setColor(0xffffff); - pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their warns now`); - pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); + if (type === 'warn_type') { // If chosen warn within options + let { count } = await Warnings.findAndCountAll({ // Grab all warns + where: { + user_id: member.id + } + }); - eventLogEmbed.setAuthor({ - name: user.tag, - iconURL: user.avatarURL() - }); - eventLogEmbed.setColor(0xffffff); - eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); - eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setTitle('_Member Pardoned_'); - eventLogEmbed.setFields( - { - name: 'User', - value: `<@${user.id}>` - }, - { - name: 'User ID', - value: user.id - }, - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Type', - value: 'Warn Pardon' - }, - { - name: 'Reason', - value: reason - } - ); - eventLogEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); + // Ease of reading instead of searching for "count" + let userWarns = count; + + if (userWarns < 1 || isNaN(userWarns)) { // User has no warns + throw new Error('This user has no warnings. Ensure you are using the correct user'); + } else { // User has warns + pardonedListEmbed.setTitle('User Pardoned'); + pardonedListEmbed.setColor(0xffffff); + pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their warns now`); + pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); - await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffffff); + eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); + eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setTitle('_Member Pardoned_'); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Warn Pardon' + }, + { + name: 'Reason', + value: reason + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); - userWarns--; // Pardon the warn + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); - // Dm the user - pardonDmEmbed.setTitle('_Member Pardoned_'); - pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); - pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); - pardonDmEmbed.setColor(0xffffff); - pardonDmEmbed.setTimestamp(Date.now()); - pardonDmEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - pardonDmEmbed.setFields( - { - name: 'Reason', - value: reason - }, - { - name: 'Total Warnings', - value: userWarns.toString() - }, - { - name: 'Warnings Left Until Kick', - value: Math.max(0, 3 - userWarns).toString() - }, - { - name: 'Warnings Left Until Ban', - value: Math.max(0, 4 - userWarns).toString() - } - ); + const targetedWarn = await Warnings.findOne({ + where: { + user_id: member.id + } + }); - await member.send({ - embeds: [pardonDmEmbed], - files: [image] - }).catch(() => console.log('Failed to DM user')); + await targetedWarn.destroy(); // Delete a warn - pardonedListEmbed.addField(`${member.user.username}'s warns`, userWarns.toString(), true); - pardonedListEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - pardonedListEmbed.setTimestamp(Date.now()); - } - } else if (type === 'kick_type') { - let { userKicks } = await Kicks.findAndCountAll({ // Grab all kicks - where: { - user_id: member.id - } - }); + userWarns--; // Cheap way to keep count - if (userKicks < 1 || isNaN(userKicks)) { // User has no kicks - throw new Error('This user has no kicks. Ensure you are using the correct user.'); - } else { // User has kicks - pardonedListEmbed.setTitle('User Pardoned'); - pardonedListEmbed.setColor(0xffffff); - pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their kicks now`); - pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); + // Dm the user + pardonDmEmbed.setTitle('_Member Pardoned_'); + pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); + pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); + pardonDmEmbed.setColor(0xffffff); + pardonDmEmbed.setTimestamp(Date.now()); + pardonDmEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonDmEmbed.setFields( + { + name: 'Reason', + value: reason + }, + { + name: 'Total Warnings', + value: userWarns.toString() + }, + { + name: 'Warnings Left Until Kick', + value: Math.max(0, 3 - userWarns).toString() + }, + { + name: 'Warnings Left Until Ban', + value: Math.max(0, 4 - userWarns).toString() + } + ); - eventLogEmbed.setAuthor({ - name: user.tag, - iconURL: user.avatarURL() - }); - eventLogEmbed.setColor(0xffffff); - eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); - eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setTitle('_Member Pardoned_'); - eventLogEmbed.setFields( - { - name: 'User', - value: `<@${user.id}>` - }, - { - name: 'User ID', - value: user.id - }, - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Type', - value: 'Kick Pardon' - }, - { - name: 'Reason', - value: reason - } - ); - eventLogEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); + await member.send({ + embeds: [pardonDmEmbed], + files: [image] + }).catch(() => console.log('Failed to DM user')); - await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); + pardonedListEmbed.addField(`${member.user.username}'s warns`, userWarns.toString(), true); + pardonedListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonedListEmbed.setTimestamp(Date.now()); + } + } else if (type === 'kick_type') { + let { count } = await Kicks.findAndCountAll({ // Grab all kicks + where: { + user_id: member.id + } + }); - userKicks--; // Pardon the kick + // Ease of reading instead of searching for "count" + let userKicks = count; - // Dm the user - pardonDmEmbed.setTitle('_Member Pardoned_'); - pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); - pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); - pardonDmEmbed.setColor(0xffffff); - pardonDmEmbed.setTimestamp(Date.now()); - pardonDmEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - pardonDmEmbed.setFields( - { - name: 'Pardon Reason', - value: reason - }, - { - name: 'Amount Of Times Kicked Now', - value: userKicks.toString() - } - ); + if (userKicks < 1 || isNaN(userKicks)) { // User has no kicks + throw new Error('This user has no kicks. Ensure you are using the correct user.'); + } else { // User has kicks + pardonedListEmbed.setTitle('User Pardoned'); + pardonedListEmbed.setColor(0xffffff); + pardonedListEmbed.setDescription(`${user.username} has been successfully pardoned, here is their kicks now`); + pardonedListEmbed.setThumbnail('attachment://mod-pardon.png'); - await member.send({ - embeds: [pardonDmEmbed], - files: [image] - }).catch(() => console.log('Failed to DM user')); + eventLogEmbed.setAuthor({ + name: user.tag, + iconURL: user.avatarURL() + }); + eventLogEmbed.setColor(0xffffff); + eventLogEmbed.setDescription(`${user.username} has been pardoned in Pretendo by ${executor.username}`); + eventLogEmbed.setThumbnail('attachment://mod-pardon.png'); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setTitle('_Member Pardoned_'); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Kick Pardon' + }, + { + name: 'Reason', + value: reason + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); - pardonedListEmbed.addField(`${member.user.username}'s kicks`, userKicks.toString(), true); - pardonedListEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - pardonedListEmbed.setTimestamp(Date.now()); - } - } - } - await interaction.editReply({ embeds: [pardonedListEmbed], files: [image], ephemeral: true }); + await util.sendEventLogMessage('channels.mod-logs', guild, null, eventLogEmbed, image, null); + + const targetedKick = await Kicks.findOne({ + where: { + user_id: member.id + } + }); + + await targetedKick.destroy(); // Delete one kick + + userKicks--; // Cheap way to keep count in the embed + + // Dm the user + pardonDmEmbed.setTitle('_Member Pardoned_'); + pardonDmEmbed.setDescription('You have been granted a pardon.\nYou may review the details of your pardon below'); + pardonDmEmbed.setThumbnail('attachment://mod-pardon.png'); + pardonDmEmbed.setColor(0xffffff); + pardonDmEmbed.setTimestamp(Date.now()); + pardonDmEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonDmEmbed.setFields( + { + name: 'Pardon Reason', + value: reason + }, + { + name: 'Amount Of Times Kicked Now', + value: userKicks.toString() + } + ); + + await member.send({ + embeds: [pardonDmEmbed], + files: [image] + }).catch(() => console.log('Failed to DM user')); + + pardonedListEmbed.addField(`${member.user.username}'s kicks`, userKicks.toString(), true); + pardonedListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + pardonedListEmbed.setTimestamp(Date.now()); + } + } + } + await interaction.editReply({ embeds: [pardonedListEmbed], files: [image], ephemeral: true }); } const command = new SlashCommandBuilder() - .setDefaultPermission(false) - .setName('pardon') - .setDescription('Pardon user(s)') - .addStringOption(option => { - return option.setName('type') - .setDescription('Type to pardon') - .setRequired(true) - .addChoices( - { name: 'Warn', value: 'warn_type'}, - { name: 'Kick', value: 'kick_type' } - ); - }) - .addStringOption(option => { - return option.setName('users') - .setDescription('User(s) to pardon') - .setRequired(true); - }) - .addStringOption(option => { + .setDefaultPermission(false) + .setName('pardon') + .setDescription('Pardon user(s)') + .addStringOption(option => { + return option.setName('type') + .setDescription('Type to pardon') + .setRequired(true) + .addChoices( + { name: 'Warn', value: 'warn_type'}, + { name: 'Kick', value: 'kick_type' } + ); + }) + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to pardon') + .setRequired(true); + }) + .addStringOption(option => { return option.setName('reason') .setDescription('Reason for the pardon') .setRequired(true); diff --git a/src/commands/purge.js b/src/commands/purge.js index a88b63b..649eb45 100644 --- a/src/commands/purge.js +++ b/src/commands/purge.js @@ -7,150 +7,150 @@ const util = require('../util'); * @param {Discord.CommandInteraction} interaction */ async function purgeHandler(interaction) { - await interaction.deferReply({ + await interaction.deferReply({ ephemeral: true }); - const guild = await interaction.guild.fetch(); - const executingMember = await interaction.member.fetch(); - const executor = executingMember.user; - const deleteAmount = interaction.options.getInteger('amount'); - const users = interaction.options.getString('users'); + const guild = await interaction.guild.fetch(); + const executingMember = await interaction.member.fetch(); + const executor = executingMember.user; + const deleteAmount = interaction.options.getInteger('amount'); + const users = interaction.options.getString('users'); - const eventLogEmbed = new Discord.MessageEmbed(); + const eventLogEmbed = new Discord.MessageEmbed(); - const purgeReplyEmbed = new Discord.MessageEmbed(); - const image = new Discord.MessageAttachment('./src/images/mod/mod-purge.png'); + const purgeReplyEmbed = new Discord.MessageEmbed(); + const image = new Discord.MessageAttachment('./src/images/mod/mod-purge.png'); - if (users !== null) { // if a user(s) is actually given - purgeReplyEmbed.setTitle('Messages Purged'); - purgeReplyEmbed.setColor(0xff6363); - purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); + if (users !== null) { // if a user(s) is actually given + purgeReplyEmbed.setTitle('Messages Purged'); + purgeReplyEmbed.setColor(0xff6363); + purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); - const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; - for (const userId of userIds) { - const member = await interaction.guild.members.fetch(userId); - const user = member.user; + for (const userId of userIds) { + const member = await interaction.guild.members.fetch(userId); + const user = member.user; - // Grab the user's messages - const userMessages = (await interaction.channel.messages.fetch()).filter( - (m) => m.author.id === member.id - ); - await interaction.channel.bulkDelete(userMessages); - - eventLogEmbed.setColor(0xff6363); - eventLogEmbed.setTitle('_Messages Purged_'); - eventLogEmbed.setDescription(`${user.username}'s messages deleted in ${interaction.channel.name}`); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setFields( - { - name: 'User', - value: `<@${user.id}>` - }, - { - name: 'User ID', - value: user.id - }, - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Channel', - value: `<#${interaction.channelId}>` - } - ); - eventLogEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setThumbnail('attachment://mod-purge.png'); - - await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); + // Grab the user's messages + const userMessages = (await interaction.channel.messages.fetch()).filter( + (m) => m.author.id === member.id + ); + await interaction.channel.bulkDelete(userMessages); + + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Messages Purged_'); + eventLogEmbed.setDescription(`${user.username}'s messages deleted in ${interaction.channel.name}`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Channel', + value: `<#${interaction.channelId}>` + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://mod-purge.png'); + + await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); - purgeReplyEmbed.setDescription(`${user.username} has been successfully purged, here is where the messages been purged in`); - purgeReplyEmbed.addField('User Messages were purged in', `<#${interaction.channelId}>`, true); - purgeReplyEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - purgeReplyEmbed.setTimestamp(Date.now()); - await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); - } - } else { - if (isNaN(deleteAmount) || deleteAmount < 2 || parseInt(deleteAmount) > 99) { // Either nothing is given within the delete amount OR less than 2 - throw new Error('Please insert an amount between 2-99 to delete.'); - } - - purgeReplyEmbed.setTitle('Messages Purged'); - purgeReplyEmbed.setColor(0xff6363); - purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); + purgeReplyEmbed.setDescription(`${user.username} has been successfully purged, here is where the messages been purged in`); + purgeReplyEmbed.addField('User Messages were purged in', `<#${interaction.channelId}>`, true); + purgeReplyEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + purgeReplyEmbed.setTimestamp(Date.now()); + await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); + } + } else { + if (isNaN(deleteAmount) || deleteAmount < 2 || parseInt(deleteAmount) > 99) { // Either nothing is given within the delete amount OR less than 2 + throw new Error('Please insert an amount between 2-99 to delete.'); + } + + purgeReplyEmbed.setTitle('Messages Purged'); + purgeReplyEmbed.setColor(0xff6363); + purgeReplyEmbed.setThumbnail('attachment://mod-purge.png'); - // Bulk Delete - let { size } = await interaction.channel.bulkDelete(deleteAmount); - - eventLogEmbed.setColor(0xff6363); - eventLogEmbed.setTitle('_Messages Purged_'); - eventLogEmbed.setDescription(`${size} messages deleted in <#${interaction.channelId}>`); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setFields( - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Channel', - value: `<#${interaction.channelId}>` - }, - { - name: 'Amount of Messages Deleted', - value: `${size}` - } - ); - eventLogEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - eventLogEmbed.setTimestamp(Date.now()); - eventLogEmbed.setThumbnail('attachment://mod-purge.png'); - - await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); - - purgeReplyEmbed.setDescription(`<#${interaction.channelId}> has been successfully purged, here is how many messages been purged`); - purgeReplyEmbed.addField('Amount of messages purged', `${size}`, true); - purgeReplyEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - purgeReplyEmbed.setTimestamp(Date.now()); - await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); - } + // Bulk Delete + let { size } = await interaction.channel.bulkDelete(deleteAmount); + + eventLogEmbed.setColor(0xff6363); + eventLogEmbed.setTitle('_Messages Purged_'); + eventLogEmbed.setDescription(`${size} messages deleted in <#${interaction.channelId}>`); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setFields( + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Channel', + value: `<#${interaction.channelId}>` + }, + { + name: 'Amount of Messages Deleted', + value: `${size}` + } + ); + eventLogEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + eventLogEmbed.setTimestamp(Date.now()); + eventLogEmbed.setThumbnail('attachment://mod-purge.png'); + + await util.sendEventLogMessage('channels.mod-logs', guild, interaction.channelId, eventLogEmbed, image, null); + + purgeReplyEmbed.setDescription(`<#${interaction.channelId}> has been successfully purged, here is how many messages been purged`); + purgeReplyEmbed.addField('Amount of messages purged', `${size}`, true); + purgeReplyEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + purgeReplyEmbed.setTimestamp(Date.now()); + await interaction.editReply({ embeds: [purgeReplyEmbed], files: [image], ephemeral: true }); + } } const command = new SlashCommandBuilder() - .setDefaultPermission(false) - .setName('purge') - .setDescription('purge message(s)') - .addIntegerOption(option => { - return option.setName('amount') - .setDescription('Amount of messages deleted (2-99)') - .setRequired(false); - }) - .addStringOption(option => { - return option.setName('users') - .setDescription('User(s) to purged (will only purge within the current channel)') - .setRequired(false); - }); + .setDefaultPermission(false) + .setName('purge') + .setDescription('purge message(s)') + .addIntegerOption(option => { + return option.setName('amount') + .setDescription('Amount of messages deleted (2-99)') + .setRequired(false); + }) + .addStringOption(option => { + return option.setName('users') + .setDescription('User(s) to purged (will only purge within the current channel)') + .setRequired(false); + }); module.exports = { name: command.name, diff --git a/src/commands/warnings.js b/src/commands/warnings.js index 088dab3..344df0a 100644 --- a/src/commands/warnings.js +++ b/src/commands/warnings.js @@ -14,46 +14,46 @@ async function warningsHandler(interaction) { const guild = await interaction.guild.fetch(); const users = interaction.options.getString('users'); - const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; + const userIds = [...new Set(Array.from(users.matchAll(Discord.MessageMentions.USERS_PATTERN), match => match[1]))]; const warningListEmbed = new Discord.MessageEmbed(); warningListEmbed.setTitle('Previous User Warnings'); warningListEmbed.setColor(0xffc800); - for (const userId of userIds) { - const member = await interaction.guild.members.fetch(userId); - const user = member.user; - - const { count } = await Warnings.findAndCountAll({ - where: { - user_id: member.id - } - }); - - warningListEmbed.setDescription(`${user.username} previous warns:`) - warningListEmbed.addFields( - { - name: `${user.username}'s warns`, - value: count.toString() - }, - { - name: 'Warnings Left Until Kick', - value: Math.max(0, 3 - count).toString() - }, - { - name: 'Warnings Left Until Ban', - value: Math.max(0, 4 - count).toString() - } - ); + for (const userId of userIds) { + const member = await interaction.guild.members.fetch(userId); + const user = member.user; + + const { count } = await Warnings.findAndCountAll({ + where: { + user_id: member.id + } + }); + + warningListEmbed.setDescription(`${user.username} previous warns:`) + warningListEmbed.addFields( + { + name: `${user.username}'s warns`, + value: count.toString() + }, + { + name: 'Warnings Left Until Kick', + value: Math.max(0, 3 - count).toString() + }, + { + name: 'Warnings Left Until Ban', + value: Math.max(0, 4 - count).toString() + } + ); - warningListEmbed.setFooter({ - text: 'Pretendo Network', - iconURL: guild.iconURL() - }); - - warningListEmbed.setTimestamp(Date.now()); - } + warningListEmbed.setFooter({ + text: 'Pretendo Network', + iconURL: guild.iconURL() + }); + + warningListEmbed.setTimestamp(Date.now()); + } const image = new Discord.MessageAttachment('./src/images/mod/mod-warn.png'); warningListEmbed.setThumbnail('attachment://mod-warn.png'); diff --git a/src/events/guildBanRemove.js b/src/events/guildBanRemove.js index 6028da8..4dfda99 100644 --- a/src/events/guildBanRemove.js +++ b/src/events/guildBanRemove.js @@ -6,22 +6,22 @@ const util = require('../util'); * @param {Discord.GuildBan} ban */ async function guildBanRemoveHandler(ban) { - const guild = ban.guild; - const user = ban.user; + const guild = ban.guild; + const user = ban.user; - const auditLogs = await guild.fetchAuditLogs({ + const auditLogs = await guild.fetchAuditLogs({ limit: 1, type: 'MEMBER_BAN_REMOVE' }); const latestLog = auditLogs.entries.first(); - const { executor } = latestLog; + const { executor } = latestLog; - const eventLogEmbed = new Discord.MessageEmbed(); + const eventLogEmbed = new Discord.MessageEmbed(); const image = new Discord.MessageAttachment('./src/images/mod/mod-pardon.png'); - eventLogEmbed.setAuthor({ + eventLogEmbed.setAuthor({ name: user.tag, iconURL: user.avatarURL() }); @@ -31,29 +31,29 @@ const util = require('../util'); eventLogEmbed.setTimestamp(Date.now()); eventLogEmbed.setFields( { - name: 'User', - value: `<@${user.id}>` - }, - { - name: 'User ID', - value: user.id - }, - { - name: 'Moderator', - value: `<@${executor.id}>` - }, - { - name: 'Moderator User ID', - value: executor.id - }, - { - name: 'Type', - value: 'Ban Pardon' - }, - { - name: 'From Bot', - value: 'false' - } + name: 'User', + value: `<@${user.id}>` + }, + { + name: 'User ID', + value: user.id + }, + { + name: 'Moderator', + value: `<@${executor.id}>` + }, + { + name: 'Moderator User ID', + value: executor.id + }, + { + name: 'Type', + value: 'Ban Pardon' + }, + { + name: 'From Bot', + value: 'false' + } ); eventLogEmbed.setFooter({ text: 'Pretendo Network', diff --git a/src/events/messageDelete.js b/src/events/messageDelete.js index 4fdab2a..4cbe9c7 100644 --- a/src/events/messageDelete.js +++ b/src/events/messageDelete.js @@ -12,18 +12,6 @@ async function messageDeleteHandler(message) { const member = await message.member.fetch(); const user = member.user; - const auditLogs = await guild.fetchAuditLogs({ - limit: 1, - type: 'MESSAGE_DELETE' - }); - - const latestLog = auditLogs.entries.first(); - - const { executor } = latestLog; - - if (executor.id == guild.me.id) return; - - const messageContent = message.content.length > 1024 ? message.content.substr(0, 1023) + '…' : message.content; const eventLogEmbed = new Discord.MessageEmbed(); diff --git a/src/util.js b/src/util.js index e4ea17e..0e108ba 100644 --- a/src/util.js +++ b/src/util.js @@ -35,7 +35,12 @@ async function sendEventLogMessage(logType, guild, originId, embed, file, compon if (!logChannel) { console.log('Missing log channel!'); } else { + // Check if there's an attached component + if (components) { await logChannel.send({ embeds: [embed], files: [file], components: [components] }); + } else { + await logChannel.send({ embeds: [embed], files: [file] }); + } } } From f22dfec83bacf5a11cc47ca177ce93831d59701e Mon Sep 17 00:00:00 2001 From: Director Reiuji <111249711+DirectorReiuji@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:09:13 -0600 Subject: [PATCH 15/26] Added README banner --- src/images/misc/chubby-banner.png | Bin 0 -> 68086 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/images/misc/chubby-banner.png diff --git a/src/images/misc/chubby-banner.png b/src/images/misc/chubby-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..c181b528d556ec68fe8312c6744c1395f5fe26a3 GIT binary patch literal 68086 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�>Xk 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