Skip to content

Commit a131a2f

Browse files
authored
Quarantines and Honey Pot channel (#364)
* Quarantines * Switch to timeout instead of quarantine role * Address reviews * Make it build * Testing. * Reference messages and unactioned quarantines. * Use proper timestamp API from JDA. * Switch to dropdown box. * Fix timestamp * Fix spelling and use the correct actionId * Fix forwarding * Fix IDs * Bans aren't temporary, remove timeouts on kick and ban. * Check all messages in honey pot on start. * Switch options around. * Only send messages into the mod channel if they were not actioned there. * Fix variable name * Only spam quarantine if in a different channel each time, update auto mod quarantine message * Change wording from "quaratine" to timeouts and temporary restrictions
1 parent 5597f4f commit a131a2f

12 files changed

Lines changed: 780 additions & 198 deletions

File tree

src/main/java/org/geysermc/discordbot/GeyserBot.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ public static void main(String[] args) throws IOException {
253253
new ExploitHandler(),
254254
new WebhookLinkPreviewHandler(),
255255
new ReadyListener(),
256+
new HoneyPotHandler(),
257+
new QuarantineHandler(),
258+
new SpamHandler(),
256259
client.build(),
257260
tagClient.build())
258261
.build();

src/main/java/org/geysermc/discordbot/commands/administration/SettingsCommand.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ public SettingsCommand() {
6868
.addChoice("Don't log","dont-log")
6969
.addChoice("Forum Channel", "forum-channel")
7070
.addChoice("Health Checks", "health-checks")
71+
.addChoice("Honey Pot Channel", "honey-pot-channel")
7172
.addChoice("Log channel", "log-channel")
73+
.addChoice("Moderation channel", "moderation-channel")
74+
.addChoice("Moderation role", "moderation-role")
7275
.addChoice("Preview Channel", "preview-channel")
7376
.addChoice("Preview Feeds Channel", "preview-feeds-channel")
7477
.addChoice("Punishment Message", "punishment-message")

src/main/java/org/geysermc/discordbot/commands/moderation/BanCommand.java

Lines changed: 3 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,17 @@
3030
import com.jagrosh.jdautilities.command.SlashCommandEvent;
3131
import net.dv8tion.jda.api.EmbedBuilder;
3232
import net.dv8tion.jda.api.Permission;
33-
import net.dv8tion.jda.api.entities.Guild;
3433
import net.dv8tion.jda.api.entities.Member;
35-
import net.dv8tion.jda.api.entities.MessageEmbed;
36-
import net.dv8tion.jda.api.entities.User;
3734
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
3835
import net.dv8tion.jda.api.interactions.commands.OptionType;
3936
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
40-
import org.geysermc.discordbot.GeyserBot;
41-
import org.geysermc.discordbot.storage.ServerSettings;
4237
import org.geysermc.discordbot.util.BotColors;
4338
import org.geysermc.discordbot.util.BotHelpers;
39+
import org.geysermc.discordbot.util.ModerationHelper;
4440

45-
import java.time.Instant;
4641
import java.util.ArrayList;
4742
import java.util.Arrays;
4843
import java.util.List;
49-
import java.util.concurrent.TimeUnit;
5044

5145
public class BanCommand extends SlashCommand {
5246

@@ -76,7 +70,7 @@ protected void execute(SlashCommandEvent event) {
7670
boolean silent = event.optBoolean("silent", false);
7771
String reason = event.optString("reason", "*None*");
7872

79-
event.replyEmbeds(handle(member, moderator, event.getGuild(), days, silent, reason)).queue();
73+
event.replyEmbeds(ModerationHelper.banUser(member, moderator, event.getGuild(), days, silent, reason, event.getChannel())).queue();
8074
}
8175

8276
@Override
@@ -136,75 +130,6 @@ protected void execute(CommandEvent event) {
136130
reason = reasonParts;
137131
}
138132

139-
event.getMessage().replyEmbeds(handle(member, moderator, event.getGuild(),delDays, silent, reason)).queue();
140-
}
141-
142-
public static MessageEmbed handle(Member member, Member moderator, Guild guild, int days, boolean silent, String reason) {
143-
// Check the user exists
144-
if (member == null) {
145-
return new EmbedBuilder()
146-
.setTitle("Invalid user")
147-
.setDescription("The user ID specified doesn't link with any valid user in this server.")
148-
.setColor(BotColors.FAILURE.getColor())
149-
.build();
150-
}
151-
152-
// Check we can target the user
153-
if (!BotHelpers.canTarget(moderator, member)) {
154-
return new EmbedBuilder()
155-
.setTitle("Higher role")
156-
.setDescription("Either the bot or you cannot target that user.")
157-
.setColor(BotColors.FAILURE.getColor())
158-
.build();
159-
}
160-
161-
User user = member.getUser();
162-
163-
// Let the user know they're banned if we are not being silent
164-
if (!silent) {
165-
user.openPrivateChannel().queue((channel) -> {
166-
EmbedBuilder embedBuilder = new EmbedBuilder()
167-
.setTitle("You have been banned from GeyserMC!")
168-
.addField("Reason", reason, false)
169-
.setTimestamp(Instant.now())
170-
.setColor(BotColors.FAILURE.getColor());
171-
172-
String punishmentMessage = GeyserBot.storageManager.getServerPreference(guild.getIdLong(), "punishment-message");
173-
if (punishmentMessage != null && !punishmentMessage.isEmpty()) {
174-
embedBuilder.addField("Additional Info", punishmentMessage, false);
175-
}
176-
177-
channel.sendMessageEmbeds(embedBuilder.build()).queue(message -> {
178-
// Ban user
179-
guild.ban(user, days, TimeUnit.DAYS).reason(reason).queue();
180-
}, throwable -> {
181-
// Ban user
182-
guild.ban(user, days, TimeUnit.DAYS).reason(reason).queue();
183-
});
184-
}, throwable -> {
185-
// Ban user
186-
guild.ban(user, days, TimeUnit.DAYS).reason(reason).queue();
187-
});
188-
} else {
189-
// Ban user
190-
guild.ban(user, days, TimeUnit.DAYS).reason(reason).queue();
191-
}
192-
193-
// Log the change
194-
int id = GeyserBot.storageManager.addLog(moderator, "ban", user, reason);
195-
196-
MessageEmbed bannedEmbed = new EmbedBuilder()
197-
.setTitle("Banned user")
198-
.addField("User", user.getAsMention(), false)
199-
.addField("Staff member", moderator.getAsMention(), false)
200-
.addField("Reason", reason, false)
201-
.setFooter("ID: " + id)
202-
.setTimestamp(Instant.now())
203-
.setColor(BotColors.SUCCESS.getColor())
204-
.build();
205-
206-
// Send the embed as a reply and to the log
207-
ServerSettings.getLogChannel(guild).sendMessageEmbeds(bannedEmbed).queue();
208-
return bannedEmbed;
133+
event.getMessage().replyEmbeds(ModerationHelper.banUser(member, moderator, event.getGuild(), delDays, silent, reason, event.getChannel())).queue();
209134
}
210135
}

src/main/java/org/geysermc/discordbot/commands/moderation/KickCommand.java

Lines changed: 3 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,13 @@
3030
import com.jagrosh.jdautilities.command.SlashCommandEvent;
3131
import net.dv8tion.jda.api.EmbedBuilder;
3232
import net.dv8tion.jda.api.Permission;
33-
import net.dv8tion.jda.api.entities.Guild;
3433
import net.dv8tion.jda.api.entities.Member;
35-
import net.dv8tion.jda.api.entities.MessageEmbed;
36-
import net.dv8tion.jda.api.entities.User;
3734
import net.dv8tion.jda.api.interactions.commands.OptionType;
3835
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
39-
import org.geysermc.discordbot.GeyserBot;
40-
import org.geysermc.discordbot.storage.ServerSettings;
4136
import org.geysermc.discordbot.util.BotColors;
4237
import org.geysermc.discordbot.util.BotHelpers;
38+
import org.geysermc.discordbot.util.ModerationHelper;
4339

44-
import java.time.Instant;
4540
import java.util.ArrayList;
4641
import java.util.Arrays;
4742
import java.util.List;
@@ -82,7 +77,7 @@ protected void execute(SlashCommandEvent event) {
8277
}
8378

8479

85-
event.replyEmbeds(handle(member, moderator, event.getGuild(), silent, reason)).queue();
80+
event.replyEmbeds(ModerationHelper.kickUser(member, moderator, event.getGuild(), silent, reason, event.getChannel())).queue();
8681
}
8782

8883
@Override
@@ -128,76 +123,6 @@ protected void execute(CommandEvent event) {
128123
reason = reasonParts;
129124
}
130125

131-
event.getMessage().replyEmbeds(handle(member, moderator, event.getGuild(), silent, reason)).queue();
132-
}
133-
134-
private MessageEmbed handle(Member member, Member moderator, Guild guild, boolean silent, String reason) {
135-
// Check user is valid
136-
if (member == null) {
137-
return new EmbedBuilder()
138-
.setTitle("Invalid user")
139-
.setDescription("The user ID specified doesn't link with any valid user in this server.")
140-
.setColor(BotColors.FAILURE.getColor())
141-
.build();
142-
}
143-
144-
// Check we can target the user
145-
if (!BotHelpers.canTarget(moderator, member)) {
146-
return new EmbedBuilder()
147-
.setTitle("Higher role")
148-
.setDescription("Either the bot or you cannot target that user.")
149-
.setColor(BotColors.FAILURE.getColor())
150-
.build();
151-
}
152-
153-
// Get the user from the member
154-
User user = member.getUser();
155-
156-
// Let the user know they're banned if we are not being silent
157-
if (!silent) {
158-
user.openPrivateChannel().queue((channel) -> {
159-
EmbedBuilder embedBuilder = new EmbedBuilder()
160-
.setTitle("You have been kicked from GeyserMC!")
161-
.addField("Reason", reason, false)
162-
.setTimestamp(Instant.now())
163-
.setColor(BotColors.FAILURE.getColor());
164-
165-
String punishmentMessage = GeyserBot.storageManager.getServerPreference(guild.getIdLong(), "punishment-message");
166-
if (punishmentMessage != null && !punishmentMessage.isEmpty()) {
167-
embedBuilder.addField("Additional Info", punishmentMessage, false);
168-
}
169-
170-
channel.sendMessageEmbeds(embedBuilder.build()).queue(message -> {
171-
// Kick user
172-
guild.kick(user).reason(reason).queue();
173-
}, throwable -> {
174-
// Kick user
175-
guild.kick(user).reason(reason).queue();
176-
});
177-
}, throwable -> {
178-
// Kick user
179-
guild.kick(user).reason(reason).queue();
180-
});
181-
} else {
182-
// Kick user
183-
guild.kick(user).reason(reason).queue();
184-
}
185-
186-
// Log the change
187-
int id = GeyserBot.storageManager.addLog(moderator, "kick", user, reason);
188-
189-
MessageEmbed kickEmbed = new EmbedBuilder()
190-
.setTitle("Kicked user")
191-
.addField("User", user.getAsMention(), false)
192-
.addField("Staff member", moderator.getAsMention(), false)
193-
.addField("Reason", reason, false)
194-
.setFooter("ID: " + id)
195-
.setTimestamp(Instant.now())
196-
.setColor(BotColors.SUCCESS.getColor())
197-
.build();
198-
199-
// Send the embed as a reply and to the log
200-
ServerSettings.getLogChannel(guild).sendMessageEmbeds(kickEmbed).queue();
201-
return kickEmbed;
126+
event.getMessage().replyEmbeds(ModerationHelper.kickUser(member, moderator, event.getGuild(), silent, reason, event.getChannel())).queue();
202127
}
203128
}

src/main/java/org/geysermc/discordbot/context_menus/QuickBanUserMenu.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import com.jagrosh.jdautilities.command.UserContextMenu;
2929
import com.jagrosh.jdautilities.command.UserContextMenuEvent;
3030
import net.dv8tion.jda.api.Permission;
31-
import org.geysermc.discordbot.commands.moderation.BanCommand;
31+
import org.geysermc.discordbot.util.ModerationHelper;
3232

3333
public class QuickBanUserMenu extends UserContextMenu {
3434
public QuickBanUserMenu() {
@@ -40,6 +40,6 @@ public QuickBanUserMenu() {
4040

4141
@Override
4242
protected void execute(UserContextMenuEvent event) {
43-
event.replyEmbeds(BanCommand.handle(event.getTargetMember(), event.getMember(), event.getGuild(), 1, false, "Scammer or compromised account")).setEphemeral(true).queue();
43+
event.replyEmbeds(ModerationHelper.banUser(event.getTargetMember(), event.getMember(), event.getGuild(), 1, false, "Scammer or compromised account", null)).setEphemeral(true).queue();
4444
}
4545
}

src/main/java/org/geysermc/discordbot/listeners/AutoModHandler.java

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.geysermc.discordbot.GeyserBot;
3939
import org.geysermc.discordbot.storage.ServerSettings;
4040
import org.geysermc.discordbot.util.BotColors;
41+
import org.geysermc.discordbot.util.ModerationHelper;
4142

4243
import javax.annotation.Nonnull;
4344

@@ -79,45 +80,6 @@ public void onAutoModExecution(@Nonnull AutoModExecutionEvent event) {
7980
Member member = event.getGuild().getMemberById(userId);
8081
if (member == null) return;
8182

82-
Guild guild = event.getGuild();
83-
String reason = "Suspected account compromise";
84-
85-
User user = member.getUser();
86-
user.openPrivateChannel().queue((channel) -> {
87-
MessageEmbed embed = new EmbedBuilder()
88-
.setTitle("You have been automatically kicked from " + guild.getName() + "!")
89-
.addField("Reason", reason, false)
90-
.addField("Recommended Actions", "Change your Discord password, enable 2FA, and scan your computer for malware. See [Discord's article](https://support.discord.com/hc/en-us/articles/24160905919511-My-Discord-Account-was-Hacked-or-Compromised) for more info.", false)
91-
.setTimestamp(Instant.now())
92-
.setColor(BotColors.WARNING.getColor())
93-
.build();
94-
95-
channel.sendMessageEmbeds(embed).queue(message -> {
96-
// Kick user
97-
guild.kick(user).reason(reason).queue();
98-
}, throwable -> {
99-
// Kick user
100-
guild.kick(user).reason(reason).queue();
101-
});
102-
}, throwable -> {
103-
// Kick user
104-
guild.kick(member).reason(reason).queue();
105-
});
106-
107-
108-
Member selfMember = guild.getSelfMember();
109-
int id = GeyserBot.storageManager.addLog(selfMember, "kick", user, reason);
110-
111-
MessageEmbed logEmbed = new EmbedBuilder()
112-
.setTitle("Kicked user")
113-
.addField("User", user.getAsMention(), false)
114-
.addField("Staff member", selfMember.getAsMention(), false)
115-
.addField("Reason", reason, false)
116-
.setFooter("ID: " + id)
117-
.setTimestamp(Instant.now())
118-
.setColor(BotColors.WARNING.getColor())
119-
.build();
120-
121-
ServerSettings.getLogChannel(guild).sendMessageEmbeds(logEmbed).queue();
83+
ModerationHelper.quarantineMember(member, event.getGuild(), "Suspected account compromise (Automod detection)", true, null, null, true);
12284
}
12385
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2026 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/GeyserDiscordBot
24+
*/
25+
26+
package org.geysermc.discordbot.listeners;
27+
28+
import net.dv8tion.jda.api.entities.Guild;
29+
import net.dv8tion.jda.api.entities.Message;
30+
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
31+
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
32+
import net.dv8tion.jda.api.events.session.ReadyEvent;
33+
import net.dv8tion.jda.api.hooks.ListenerAdapter;
34+
import org.geysermc.discordbot.GeyserBot;
35+
import org.geysermc.discordbot.util.ModerationHelper;
36+
import org.jetbrains.annotations.NotNull;
37+
38+
import java.util.List;
39+
40+
public class HoneyPotHandler extends ListenerAdapter {
41+
@Override
42+
public void onReady(@NotNull ReadyEvent event) {
43+
for (Guild guild : event.getJDA().getGuilds()) {
44+
String honeyPotChannelId = GeyserBot.storageManager.getServerPreference(guild.getIdLong(), "honey-pot-channel");
45+
if (honeyPotChannelId == null) continue;
46+
47+
TextChannel channel = guild.getTextChannelById(honeyPotChannelId);
48+
if (channel == null) continue;
49+
50+
channel.getHistory().retrievePast(100).queue(messages -> {
51+
// Add a honey pot message if the bot has no messages in the channel
52+
if (messages.stream().filter(m -> m.getAuthor().getId().equals(guild.getSelfMember().getId())).toList().isEmpty()) {
53+
channel.sendMessage("""
54+
# DO NOT POST ANY MESSAGES HERE
55+
56+
This is a honey pot channel designed to catch scam accounts. Sending a message here will lead to a temporary restriction of your account in this server.
57+
""").queue();
58+
}
59+
60+
messages.stream().filter(m -> !m.getAuthor().getId().equals(guild.getSelfMember().getId())).forEach(message -> {
61+
ModerationHelper.quarantineMember(message.getMember(), message.getGuild(), "Messaged in the honey pot channel.", false, null, message, true);
62+
});
63+
});
64+
}
65+
}
66+
67+
@Override
68+
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
69+
if (event.getAuthor().isBot()) return;
70+
if (!event.isFromGuild()) return;
71+
72+
String honeyPotChannelId = GeyserBot.storageManager.getServerPreference(event.getGuild().getIdLong(), "honey-pot-channel");
73+
if (honeyPotChannelId == null) return;
74+
75+
if (event.getChannel().getId().equals(honeyPotChannelId)) {
76+
ModerationHelper.quarantineMember(event.getMember(), event.getGuild(), "Messaged in the honey pot channel.", false, null, event.getMessage(), true);
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)