From d6a0645e81bac4448ca227db09b0aee8da20fb3f Mon Sep 17 00:00:00 2001 From: toast-ts <96593068+toast-ts@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:09:33 +1100 Subject: [PATCH] Moderation improvements --- src/commands/case.ts | 6 +++--- src/commands/unpunish.ts | 1 + src/components/Punish.ts | 2 +- src/events/guildMemberAdd.ts | 2 +- src/index.ts | 5 +++++ src/interfaces.d.ts | 1 + src/models/punishments.ts | 17 ++++++++++++----- 7 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/commands/case.ts b/src/commands/case.ts index b29734b..d3be107 100644 --- a/src/commands/case.ts +++ b/src/commands/case.ts @@ -8,7 +8,7 @@ export default class Case { for (const channelID of logsArray) { const channel = await client.channels.fetch(channelID) as Discord.TextChannel; if (channel && channel.type === Discord.ChannelType.GuildText) { - const messages = await channel.messages.fetch({limit: 3}); + const messages = await channel.messages.fetch({limit: 5}); messages.forEach(async message=>{ if (message?.embeds[0]?.title && message?.embeds[0]?.title.match(new RegExp(`Case #${caseId}`))) { const findIndex = message?.embeds[0].fields.findIndex(x=>x.name === 'Reason'); @@ -36,8 +36,8 @@ export default class Case { const cancelledBy = punishment.dataValues.expired ? await client.punishments.findByCancels(punishment.dataValues.case_id) : null; const cancels = punishment.dataValues.cancels ? await client.punishments.findCase(punishment.dataValues.cancels) : null; const embed = new client.embed().setColor(client.config.embedColor).setTimestamp(Number(punishment.dataValues.time)).setTitle(`${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`).addFields( - {name: 'User', value: `${MessageTool.formatMention(punishment.dataValues.member, 'user')} \`${punishment.dataValues.member}\``, inline: true}, - {name: 'Moderator', value: `${MessageTool.formatMention(punishment.dataValues.moderator, 'user')} \`${punishment.dataValues.moderator}\``, inline: true}, + {name: 'User', value: `${punishment.member_name}\n${MessageTool.formatMention(punishment.dataValues.member, 'user')}\n\`${punishment.dataValues.member}\``, inline: true}, + {name: 'Moderator', value: `${client.users.resolve(punishment.moderator).tag}\n${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}\n\`${punishment.dataValues.moderator}\``, inline: true}, {name: '\u200b', value: '\u200b', inline: true}, {name: 'Reason', value: `\`${punishment.reason || 'Reason unspecified'}\``, inline: true}) if (punishment.dataValues.duration) embed.addFields({name: 'Duration', value: `${Formatters.timeFormat(punishment.dataValues.duration, 100)}`}) diff --git a/src/commands/unpunish.ts b/src/commands/unpunish.ts index f530356..7c61bd7 100644 --- a/src/commands/unpunish.ts +++ b/src/commands/unpunish.ts @@ -8,6 +8,7 @@ export default class Unpunish { const punishment = await client.punishments.findCase(interaction.options.getInteger('case_id', true)); if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true}); if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true}); + if (punishment.dataValues.expired) return interaction.reply({content: 'This case ID is already expired.', ephemeral: true}); const reason = interaction.options.getString('reason') ?? 'Reason unspecified'; await client.punishments.punishmentRemove(punishment.dataValues.case_id, interaction.user.id, reason, interaction); diff --git a/src/components/Punish.ts b/src/components/Punish.ts index 2dd414a..89b6057 100644 --- a/src/components/Punish.ts +++ b/src/components/Punish.ts @@ -16,7 +16,7 @@ export default async(client:TClient, interaction: Discord.ChatInputCommandIntera Logger.console('log', 'PunishmentLog', punishLog); (client.channels.cache.get(client.config.dcServer.channels.punishment_log) as Discord.TextChannel).send({embeds:[new client.embed().setColor(client.config.embedColor).setAuthor({name: interaction.user.username, iconURL: interaction.user.avatarURL({size:2048})}).setTitle('Punishment Log').setDescription(punishLog).setTimestamp()]}); - if (!GuildMember.moderatable) return interaction.reply(`I cannot ${type} this user.`); + if (GuildMember && !GuildMember?.moderatable) return interaction.reply(`I cannot ${type} this user.`); if (interaction.user.id === User.id) return interaction.reply(`You cannot ${type} yourself.`); if (!GuildMember && !['unban', 'ban'].includes(type)) return interaction.reply(`You cannot ${type} someone who is not in the server.`); if (User.bot) return interaction.reply(`You cannot ${type} a bot!`); diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index 3aae464..6b9bcef 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -23,7 +23,7 @@ export default class GuildMemberAdd { const usedInvite = newInvites.find((inv:Discord.Invite)=>client.invites.get(inv.code)?.uses < inv.uses); newInvites.forEach((inv:Discord.Invite)=>client.invites.set(inv.code,{uses: inv.uses, creator: inv.inviterId, channel: inv.channel.name})); (client.channels.resolve(client.config.dcServer.channels.logs) as Discord.TextChannel).send({embeds: [ - new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`${isBot} Joined: ${member.user.username}`).setFooter({text: `Total members: ${index}${suffix}`}).addFields( + new client.embed().setColor(client.config.embedColorGreen).setTimestamp().setThumbnail(member.user.displayAvatarURL({size: 2048})).setTitle(`${isBot} Joined: ${member.user.username}`).setFooter({text: `Total members: ${index}${suffix} | ID: ${member.user.id}`}).addFields( {name: '🔹 Account Creation Date', value: `\n`}, {name: '🔹 Invite Data:', value: usedInvite ? `Invite: \`${usedInvite.code}\`\nCreated by: **${usedInvite.inviter?.username}**\nChannel: **#${usedInvite.channel.name}**` : 'No invite data could be fetched.'} )]}); diff --git a/src/index.ts b/src/index.ts index 249af9e..7417774 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ const client = new TClient; client.init(); import Logger from './helpers/Logger.js'; import YTModule from './modules/YTModule.js'; +import CacheServer from './components/CacheServer.js'; import MPModule, {refreshTimerSecs} from './modules/MPModule.js'; import UsernameHelper from './helpers/UsernameHelper.js'; import {Punishment, RawGatewayPacket, RawMessageDelete, RawMessageUpdate} from 'src/interfaces'; @@ -52,6 +53,10 @@ setInterval(async()=>{ const punishments = await client.punishments.findInCache(); punishments.filter((x:Punishment)=>x.endTime && x.endTime <= now && !x.expired).forEach(async (punishment:Punishment)=>{ + let key = `punishment_handled:${punishment.case_id}`; + if (await CacheServer.get(key, false)) return; + await CacheServer.set(key, true, false).then(async()=>await CacheServer.expiry(key, 35)); + Logger.console('log', 'Punishment', `${punishment.member}\'s ${punishment.type} should expire now`); Logger.console('log', 'Punishment', await client.punishments.punishmentRemove(punishment.case_id, client.user.id, 'Time\'s up!')); }); diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 67e4e10..0d2a23f 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -3,6 +3,7 @@ import {ColorResolvable, PresenceData, APIUser} from 'discord.js'; export interface Punishment { case_id: number; type: string; + member_name: string; member: string; moderator: string; expired?: boolean; diff --git a/src/models/punishments.ts b/src/models/punishments.ts index 6430460..8f37da3 100644 --- a/src/models/punishments.ts +++ b/src/models/punishments.ts @@ -11,6 +11,7 @@ import Formatters from '../helpers/Formatters.js'; class punishments extends Model { declare public case_id: number; declare public type: string; + declare public member_name: string; declare public member: string; declare public moderator: string; declare public expired: boolean; @@ -38,6 +39,10 @@ export class PunishmentsSvc { type: DataTypes.STRING, allowNull: false }, + member_name: { + type: DataTypes.STRING, + allowNull: true + }, member: { type: DataTypes.STRING, allowNull: false @@ -124,7 +129,7 @@ export class PunishmentsSvc { .setColor(this.client.config.embedColor) .setTitle(`${punishment.type[0].toUpperCase() + punishment.type.slice(1)} | Case #${punishment.case_id}`) .addFields( - {name: '🔹 User', value: `<@${punishment.member}>\n\`${punishment.member}\``, inline: true}, + {name: '🔹 User', value: `${punishment.member_name}\n<@${punishment.member}>\n\`${punishment.member}\``, inline: true}, {name: '🔹 Moderator', value: `<@${punishment.moderator}>\n\`${punishment.moderator}\``, inline: true}, {name: '\u200b', value: '\u200b', inline: true}, {name: '🔹 Reason', value: `\`${punishment.reason}\``, inline: true} @@ -150,7 +155,7 @@ export class PunishmentsSvc { const {time, interaction} = options; const now = Date.now(); const guild = this.client.guilds.cache.get(this.client.config.dcServer.id) as Discord.Guild; - const punishment:Punishment = {type, case_id: await this.generateCaseId(), member: user.id, reason, moderator, time: now}; + const punishment:Punishment = {type, case_id: await this.generateCaseId(), member_name: user.username, member: user.id, reason, moderator, time: now}; const inOrFromBoolean = ['warn', 'mute', 'remind'].includes(type) ? 'in' : 'from'; const auditLogReason = `${reason ?? 'Reason unspecified'} | Case #${punishment.case_id}`; const embed = new this.client.embed() @@ -172,7 +177,7 @@ export class PunishmentsSvc { if (['ban', 'softban'].includes(type)) { const alreadyBanned = await guild.bans.fetch(user.id).catch(()=>null); // 172800 seconds is 48 hours, just for future reference if (!alreadyBanned) punishmentResult = await guild.bans.create(user.id, {reason: auditLogReason, deleteMessageSeconds: 172800}).catch((err:Error)=>err.message); - else punishmentResult = 'This user already exists in the guild\'s ban list.'; + else punishmentResult = `This user already exists in the guild\'s ban list.\nReason: \`${alreadyBanned?.reason}\``; } else if (type === 'kick') punishmentResult = await guildUser?.kick(auditLogReason).catch((err:Error)=>err.message); else if (type === 'mute') punishmentResult = await guildUser?.timeout(millisecondTime, auditLogReason).catch((err:Error)=>err.message); @@ -192,6 +197,7 @@ export class PunishmentsSvc { else await this.model.create({ case_id: punishment.case_id, type: punishment.type, + member_name: punishment.member_name, member: punishment.member, moderator: punishment.moderator, expired: punishment.expired, @@ -217,7 +223,7 @@ export class PunishmentsSvc { const user = await this.client.users.fetch(punishment.member); const guildUser:Discord.GuildMember = await guild.members.fetch(punishment.member).catch(()=>null); - let removePunishmentData:Punishment = {type: `un${punishment.type}`, case_id: ID, cancels: punishment.case_id, member: punishment.member, reason, moderator, time: now}; + let removePunishmentData:Punishment = {type: `un${punishment.type}`, case_id: ID, cancels: punishment.case_id, member_name: punishment.member_name, member: punishment.member, reason, moderator, time: now}; let removePunishmentResult:any; if (punishment.type === 'ban') removePunishmentResult = await guild.bans.remove(punishment.member, auditLogReason).catch((err:Error)=>err.message); @@ -236,6 +242,7 @@ export class PunishmentsSvc { this.model.create({ case_id: removePunishmentData.case_id, type: removePunishmentData.type, + member_name: removePunishmentData.member_name, member: removePunishmentData.member, moderator: removePunishmentData.moderator, expired: removePunishmentData.expired, @@ -252,7 +259,7 @@ export class PunishmentsSvc { .setColor(this.client.config.embedColor) .setTitle(`${removePunishmentData.type[0].toUpperCase() + removePunishmentData.type.slice(1)} | Case #${removePunishmentData.case_id}`) .setDescription(`${user.username}\n<@${user.id}>\n\`${user.id}\``) - .addFields({name: 'Reason', value: `\`${reason}\``}, {name: 'Overwrites', value: `Case #${punishment.case_id}`}) + .addFields({name: 'Reason', value: `\`${reason}\``, inline: true}, {name: 'Overwrites', value: `Case #${punishment.case_id}`, inline: true}) ]}); else return `Successfully un${this.getPastTense(removePunishmentData.type.replace('un', ''))} ${user.username} (\`${user.id}\`) for ${reason}` }