Skip to content

Commit 04c8fc5

Browse files
committed
update: Updated entire mod panel system, added more checks, moved to Events, added softban
1 parent 9931fbb commit 04c8fc5

File tree

6 files changed

+706
-161
lines changed

6 files changed

+706
-161
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
"seriousrn",
7373
"showchannel",
7474
"skipto",
75+
"softban",
76+
"softbans",
7577
"spongebob",
7678
"stickyschema",
7779
"ticketmessage",

src/commands/HardModeration/modPanel.js

Lines changed: 114 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -3,174 +3,127 @@ const { SlashCommandBuilder, EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRow
33
module.exports = {
44
data: new SlashCommandBuilder()
55
.setName('mod-panel')
6-
.setDescription('Moderate a member.')
7-
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
8-
.addUserOption(option => option.setName("user").setDescription("The user you want to moderate").setRequired(true))
9-
.addStringOption(option => option.setName('reason').setDescription('Reason for moderating the member').setRequired(false))
10-
.addIntegerOption(option => option.setName('time').setDescription('This is how long the user\'s punishment is going to last (in minutes). Default: 1 hour').setRequired(false)),
11-
async execute (interaction, client) {
12-
if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) return await interaction.reply({ content: `${client.config.noPerms}`, flags: MessageFlags.Ephemeral});
6+
.setDescription('Moderate a member with various punishment options.')
7+
.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers)
8+
.addUserOption(option => option.setName("user").setDescription("The user you want to moderate").setRequired(true)),
9+
10+
async execute(interaction, client) {
11+
if (!interaction.member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) {
12+
return await interaction.reply({
13+
content: `${client.config.noPerms}`,
14+
flags: MessageFlags.Ephemeral
15+
});
16+
}
1317

14-
const target = await interaction.options.getUser(`user`);
15-
const member = await interaction.options.getMember("user")
16-
const reason = await interaction.options.getString(`reason`) || "\`\`No reason given\`\`";
17-
const length = await interaction.options.getInteger(`time`) || 60;
18-
let guild = await interaction.guild.fetch();
19-
20-
const mod_panel = new EmbedBuilder()
21-
.setAuthor({ name: `${client.user.username} Mod Panel`})
22-
.setTitle(`> ${client.config.modEmojiHard} Mod panel tool ${client.config.arrowEmoji}`)
23-
.setThumbnail(member.displayAvatarURL({ size: 1024, format: `png`, dynamic: true}))
24-
.addFields({ name: `Target:`, value: `> ${target}`, inline: true })
25-
.addFields({ name: `Target ID:`, value: `> \`${target.id}\``, inline: true })
26-
.addFields({ name: `Timeout Length:`, value: `> \`${length} minute(s)\``, inline: true })
27-
.addFields({ name: `Reason for Punishment:`, value: `> \`${reason}\``, inline: false })
28-
.setFooter({ text: `Mod panel ${client.config.devBy}`})
29-
.setTimestamp()
30-
.setColor(client.config.embedModHard)
31-
32-
const row_1 = new ActionRowBuilder()
33-
.addComponents(
34-
new ButtonBuilder()
35-
.setCustomId('timeout')
36-
.setEmoji('⏳')
37-
.setLabel('Timeout')
38-
.setStyle(ButtonStyle.Secondary),
39-
new ButtonBuilder()
40-
.setCustomId('kick')
41-
.setEmoji('🦵')
42-
.setLabel('Kick')
43-
.setStyle(ButtonStyle.Primary),
44-
new ButtonBuilder()
45-
.setCustomId('ban')
46-
.setEmoji('🛠️')
47-
.setLabel('Ban')
48-
.setStyle(ButtonStyle.Danger),
49-
new ButtonBuilder()
50-
.setLabel(`Delete`)
51-
.setCustomId(`delete`)
52-
.setEmoji('✖️')
53-
.setStyle(ButtonStyle.Danger))
18+
const target = await interaction.options.getUser('user');
19+
const member = await interaction.options.getMember('user');
20+
const guild = interaction.guild;
5421

55-
const failEmbed = new EmbedBuilder()
56-
.setColor(client.config.embedModHard)
57-
.setDescription(`Failed to moderate **${target}**.`)
58-
.setFooter({ text: `${client.user.username} Moderation Tool ${client.config.devBy}` })
22+
if (!member) {
23+
return await interaction.reply({
24+
content: "This user is not a member of this server.",
25+
flags: MessageFlags.Ephemeral
26+
});
27+
}
5928

60-
const banEmbed = new EmbedBuilder()
61-
.setColor(client.config.embedModHard)
62-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
63-
.setTitle(`> ${client.config.modEmojiHard} You were **banned** from "${guild.name}" ${client.config.arrowEmoji}`)
64-
.addFields({ name: 'Server', value: `> ${guild.name}`, inline: true})
65-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
66-
.setFooter({ text: `Banned from ${guild.name} ${client.config.devBy}`})
67-
.setTimestamp()
68-
.setThumbnail(client.user.avatarURL())
29+
if (interaction.member.id === member.id) {
30+
return await interaction.reply({
31+
content: "You cannot moderate yourself.",
32+
flags: MessageFlags.Ephemeral
33+
});
34+
}
6935

70-
const timeoutEmbed = new EmbedBuilder()
71-
.setColor(client.config.embedModHard)
72-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
73-
.setTitle(`> ${client.config.modEmojiHard} You were **Timed-out** in "${guild.name}" ${client.config.arrowEmoji}`)
74-
.addFields({ name: 'Server', value: `> ${guild.name}`, inline: true})
75-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
76-
.addFields({ name: 'Length', value: `> ${length} minute(s)`, inline: true})
77-
.setFooter({ text: `Timed-out in ${guild.name} ${client.config.devBy}`})
78-
.setTimestamp()
79-
.setThumbnail(client.user.avatarURL())
36+
if (interaction.member.roles.highest.position <= member.roles.highest.position) {
37+
return await interaction.reply({
38+
content: "You cannot moderate a member with the same or higher role than you.",
39+
flags: MessageFlags.Ephemeral
40+
});
41+
}
42+
43+
if (!member.moderatable) {
44+
return await interaction.reply({
45+
content: "I don't have permission to moderate this user. Their role may be higher than mine.",
46+
flags: MessageFlags.Ephemeral
47+
});
48+
}
49+
50+
const panelId = Date.now().toString();
8051

81-
const kickEmbed = new EmbedBuilder()
82-
.setColor(client.config.embedModHard)
83-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
84-
.setTitle(`> ${client.config.modEmojiHard} You were **kicked** from "${guild.name}" ${client.config.arrowEmoji}`)
85-
.addFields({ name: 'Server', value: `> ${guild.name}`, inline: true})
86-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
87-
.setFooter({ text: `Kicked from ${guild.name} ${client.config.devBy}`})
88-
.setTimestamp()
89-
.setThumbnail(client.user.avatarURL())
52+
const mod_panel = new EmbedBuilder()
53+
.setAuthor({ name: `${client.user.username} Moderation Panel` })
54+
.setTitle(`> ${client.config.modEmojiHard} Moderation Panel ${client.config.arrowEmoji}`)
55+
.setThumbnail(member.displayAvatarURL({ size: 1024, format: 'png', dynamic: true }))
56+
.addFields(
57+
{ name: `Target User`, value: `> ${target} (${target.tag})`, inline: true },
58+
{ name: `Target ID`, value: `> \`${target.id}\``, inline: true },
59+
{ name: `Instructions`, value: `> Select an action below. You will be prompted for additional details as needed.` }
60+
)
61+
.setFooter({ text: `Moderation panel ${client.config.devBy} • Panel ID: ${panelId}` })
62+
.setTimestamp()
63+
.setColor(client.config.embedModHard);
9064

91-
const banEmbed2 = new EmbedBuilder()
92-
.setColor(client.config.embedModHard)
93-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
94-
.setTitle(`> ${client.config.modEmojiHard} User was **\`BANNED\`** from "${guild.name}" ${client.config.arrowEmoji}`)
95-
.addFields({ name: `Moderator:`, value: `${interaction.user.tag}`, inline: true})
96-
.addFields({ name: `User:`, value: `${target}`, inline: true})
97-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
98-
.setFooter({ text: `Banned from ${guild.name} ${client.config.devBy}`})
99-
.setTimestamp()
100-
.setThumbnail(client.user.avatarURL())
65+
const actionButtons = new ActionRowBuilder()
66+
.addComponents(
67+
new ButtonBuilder()
68+
.setCustomId(`modpanel_${panelId}_${interaction.user.id}_${target.id}_timeout`)
69+
.setEmoji('⏳')
70+
.setLabel('Timeout')
71+
.setStyle(ButtonStyle.Secondary),
72+
new ButtonBuilder()
73+
.setCustomId(`modpanel_${panelId}_${interaction.user.id}_${target.id}_kick`)
74+
.setEmoji('🦵')
75+
.setLabel('Kick')
76+
.setStyle(ButtonStyle.Primary),
77+
new ButtonBuilder()
78+
.setCustomId(`modpanel_${panelId}_${interaction.user.id}_${target.id}_ban`)
79+
.setEmoji('🔨')
80+
.setLabel('Ban')
81+
.setStyle(ButtonStyle.Danger),
82+
);
83+
84+
const secondRow = new ActionRowBuilder()
85+
.addComponents(
86+
new ButtonBuilder()
87+
.setCustomId(`modpanel_${panelId}_${interaction.user.id}_${target.id}_softban`)
88+
.setEmoji('🧹')
89+
.setLabel('Soft Ban')
90+
.setStyle(ButtonStyle.Danger),
91+
new ButtonBuilder()
92+
.setCustomId(`modpanel_${panelId}_${interaction.user.id}_${target.id}_delete`)
93+
.setEmoji('✖️')
94+
.setLabel('Cancel')
95+
.setStyle(ButtonStyle.Secondary),
96+
);
10197

102-
const kickEmbed2 = new EmbedBuilder()
103-
.setColor(client.config.embedModHard)
104-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
105-
.setTitle(`> ${client.config.modEmojiHard} User was **\`KICKED\`** from "${guild.name}" ${client.config.arrowEmoji}`)
106-
.addFields({ name: `Moderator:`, value: `${interaction.user.tag}`, inline: true})
107-
.addFields({ name: `User:`, value: `${target}`, inline: true})
108-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
109-
.setFooter({ text: `Kicked from ${guild.name} ${client.config.devBy}`})
110-
.setTimestamp()
111-
.setThumbnail(client.user.avatarURL())
98+
if (!client.modPanels) client.modPanels = new Map();
11299

113-
const timeoutEmbed2 = new EmbedBuilder()
114-
.setColor(client.config.embedModHard)
115-
.setAuthor({ name: `${client.user.username} Moderation Tool`})
116-
.setTitle(`> ${client.config.modEmojiHard} User was **\`TIMED-OUT\`** from "${guild.name}" ${client.config.arrowEmoji}`)
117-
.addFields({ name: `Moderator:`, value: `${interaction.user.tag}`, inline: true})
118-
.addFields({ name: `User:`, value: `${target}`, inline: true})
119-
.addFields({ name: 'Length', value: `> ${length} minute(s)`, inline: true})
120-
.addFields({ name: 'Reason', value: `> ${reason}`, inline: true})
121-
.setFooter({ text: `Timed-out in ${guild.name} ${client.config.devBy}`})
122-
.setTimestamp()
123-
.setThumbnail(client.user.avatarURL())
124-
125-
const msg = await interaction.reply({ embeds: [mod_panel], components: [row_1] })
126-
127-
collector = msg.createMessageComponentCollector()
128-
collector.on('collect', async i => {
129-
if(i.customId == 'kick') {
130-
if (i.user.id !== interaction.user.id) {
131-
return await i.reply({ content: `Only ${interaction.user.tag} can interact with the buttons!`, flags: MessageFlags.Ephemeral})
100+
client.modPanels.set(panelId, {
101+
moderatorId: interaction.user.id,
102+
moderatorTag: interaction.user.tag,
103+
targetId: target.id,
104+
targetTag: target.tag,
105+
memberId: member.id,
106+
guildId: guild.id,
107+
guildName: guild.name,
108+
messageId: null,
109+
createdAt: Date.now(),
110+
expiryTimeout: setTimeout(() => {
111+
if (client.modPanels.has(panelId)) {
112+
client.modPanels.delete(panelId);
113+
client.logs.info(`[MOD_PANEL] Panel ${panelId} expired and was removed from cache`);
132114
}
133-
target.send({ embeds: [kickEmbed] }).catch((err) => { return client.logs.error('[MOD_PANEL_KICK] Failed to DM user.') });
134-
let kick = await guild.members.kick(target).catch((err) => {
135-
client.logs.error("Error with Kick command: " + err)
136-
})
137-
await interaction.channel.send({ embeds: [kickEmbed2] });
138-
if(!kick) {
139-
await interaction.reply({ embeds: [failEmbed], flags: MessageFlags.Ephemeral })
140-
}
141-
}
142-
if(i.customId == 'timeout') {
143-
if (i.user.id !== interaction.user.id) {
144-
return await i.reply({ content: `Only ${interaction.user.tag} can interact with the buttons!`, flags: MessageFlags.Ephemeral})
145-
}
146-
target.send({ embeds: [timeoutEmbed] }).catch((err) => { return client.logs.error('[MOD_PANEL_TIMEOUT] Failed to DM user.') });
147-
let timeout = await member.timeout(length * 60000).catch((err) => {
148-
client.logs.error("Error with timeout command: " + err)
149-
})
150-
await interaction.channel.send({ embeds: [timeoutEmbed2] });
151-
if(!timeout) {
152-
await interaction.reply({ embeds: [failEmbed], flags: MessageFlags.Ephemeral })
153-
}
154-
}
155-
if(i.customId == 'delete') {
156-
if (i.user.id !== interaction.user.id) {
157-
return await i.reply({ content: `Only ${interaction.user.tag} can interact with the buttons!`, flags: MessageFlags.Ephemeral})
158-
}
159-
interaction.deleteReply();
160-
}
161-
if(i.customId == 'ban') {
162-
if (i.user.id !== interaction.user.id) {
163-
return await i.reply({ content: `Only ${interaction.user.tag} can interact with the buttons!`, flags: MessageFlags.Ephemeral})
164-
}
165-
target.send({ embeds: [banEmbed] }).catch((err) => { return client.logs.error('[MOD_PANEL_BAN] Failed to DM user.') });
166-
await interaction.channel.send({ embeds: [banEmbed2] });
167-
let ban = await guild.members.ban(target, { reason: `${reason}`}).catch((err) => {
168-
client.logs.error("Error with Ban command: " + err)
169-
})
170-
if(!ban) {
171-
await interaction.reply({ embeds: [failEmbed], flags: MessageFlags.Ephemeral })
172-
}
173-
}
174-
})
115+
}, 180000)
116+
});
117+
118+
const response = await interaction.reply({
119+
embeds: [mod_panel],
120+
components: [actionButtons, secondRow]
121+
}).then(() => interaction.fetchReply());
122+
123+
if (client.modPanels.has(panelId)) {
124+
const panelData = client.modPanels.get(panelId);
125+
panelData.messageId = response.id;
126+
client.modPanels.set(panelId, panelData);
127+
}
175128
}
176-
}
129+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const { Events } = require('discord.js');
2+
const SoftbanEntry = require('../../schemas/softbanSystem');
3+
4+
module.exports = {
5+
name: Events.ClientReady,
6+
once: true,
7+
async execute(client) {
8+
client.logs.info('[SOFTBAN] Starting softban check system');
9+
10+
let lastCheckHadResults = false;
11+
let checkCount = 0;
12+
13+
async function checkSoftbans() {
14+
try {
15+
checkCount++;
16+
17+
const now = new Date();
18+
const expiredSoftbans = await SoftbanEntry.find({
19+
expiresAt: { $lte: now },
20+
isActive: true
21+
});
22+
23+
if (expiredSoftbans.length > 0) {
24+
client.logs.info(`[SOFTBAN] Found ${expiredSoftbans.length} expired softbans to process`);
25+
lastCheckHadResults = true;
26+
} else if (lastCheckHadResults || checkCount % 60 === 0) {
27+
client.logs.debug(`[SOFTBAN] No expired softbans to process (check #${checkCount})`);
28+
lastCheckHadResults = false;
29+
}
30+
31+
for (const softban of expiredSoftbans) {
32+
try {
33+
const guild = client.guilds.cache.get(softban.guildId);
34+
if (!guild) {
35+
client.logs.warn(`[SOFTBAN] Guild ${softban.guildId} not found for softban ID ${softban._id}`);
36+
softban.isActive = false;
37+
await softban.save();
38+
continue;
39+
}
40+
41+
await guild.members.unban(
42+
softban.userId,
43+
`[AUTOMATED] Temporary softban expired. Original reason: ${softban.reason}`
44+
).catch(error => {
45+
if (error.code === 10026) {
46+
client.logs.info(`[SOFTBAN] User ${softban.userId} is already unbanned from guild ${softban.guildId}`);
47+
} else {
48+
client.logs.error(`[SOFTBAN] Error unbanning user ${softban.userId} from guild ${softban.guildId}: ${error.message}`);
49+
}
50+
});
51+
52+
client.logs.info(`[SOFTBAN] User ${softban.userId} automatically unbanned from guild ${softban.guildId}`);
53+
54+
softban.isActive = false;
55+
await softban.save();
56+
57+
} catch (error) {
58+
client.logs.error(`[SOFTBAN] Error processing softban ${softban._id}: ${error.message}`);
59+
60+
if (error.code === 10026) {
61+
softban.isActive = false;
62+
await softban.save();
63+
}
64+
}
65+
}
66+
} catch (error) {
67+
client.logs.error(`[SOFTBAN] Error in softban check routine: ${error.message}`);
68+
}
69+
70+
setTimeout(checkSoftbans, 60000);
71+
}
72+
73+
checkSoftbans();
74+
}
75+
};

0 commit comments

Comments
 (0)