Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion application/config.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,43 @@
],
"categoryRoleSuffix": " - Helper"
},
"mediaOnlyChannelPattern": "memes"
"mediaOnlyChannelPattern": "memes",
"blacklistedFileExtension": [
"application",
"bat",
"cmd",
"com",
"cpl",
"exe",
"gadget",
"hta",
"inf"
"jse",
"lnk",
"msc",
"msh",
"msh1",
"msh1xml",
"msh2",
"msh2xml",
"mshxml",
"msi",
"msp",
"pif",
"ps1",
"ps1xml",
"ps2",
"ps2xml",
"psc1",
"psc2",
"scf",
"scr",
"vb",
"vbe",
"vbs",
"ws",
"wsc",
"wsf",
"wsh"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommand;
import org.togetherjava.tjbot.commands.mediaonly.MediaOnlyChannelListener;
import org.togetherjava.tjbot.commands.moderation.*;
import org.togetherjava.tjbot.commands.moderation.attachment.BlacklistedAttachmentListener;
import org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker;
import org.togetherjava.tjbot.commands.moderation.scam.ScamHistoryPurgeRoutine;
import org.togetherjava.tjbot.commands.moderation.scam.ScamHistoryStore;
Expand Down Expand Up @@ -92,6 +93,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new ImplicitAskListener(config, helpSystemHelper));
features.add(new MediaOnlyChannelListener(config));
features.add(new FileSharingMessageListener(config));
features.add(new BlacklistedAttachmentListener(config, modAuditLogWriter));

// Event receivers
features.add(new RejoinModerationRoleListener(actionsStore, config));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.togetherjava.tjbot.commands.moderation.attachment;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.requests.RestAction;
import org.togetherjava.tjbot.commands.MessageReceiverAdapter;
import org.togetherjava.tjbot.config.Config;
import org.togetherjava.tjbot.moderation.ModAuditLogWriter;

import javax.annotation.Nonnull;
import java.awt.*;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

/**
* Reacts to blacklisted attachments being posted, upon which they are deleted.
*/
public final class BlacklistedAttachmentListener extends MessageReceiverAdapter {
private final ModAuditLogWriter modAuditLogWriter;
private final List<String> blacklistedFileExtensions;

/**
* Creates a AttachmentListener to receive all message sent in any channel.
*
* @param config to find the blacklisted media attachments
* @param modAuditLogWriter to inform the mods about the suspicious attachment
*/
public BlacklistedAttachmentListener(Config config, ModAuditLogWriter modAuditLogWriter) {
super(Pattern.compile(".*"));
this.modAuditLogWriter = modAuditLogWriter;
this.blacklistedFileExtensions = config.getBlacklistedFileExtensions();
}

@Override
public void onMessageReceived(MessageReceivedEvent event) {
if (event.getAuthor().isBot() || event.isWebhookMessage()) {
return;
}
if (doesMessageContainBlacklistedContent(event.getMessage())) {
handleBadMessage(event.getMessage());
}
}

private void handleBadMessage(Message message) {
message.delete().flatMap(any -> dmUser(message)).queue(any -> warnMods(message));
}

@Nonnull
private RestAction<Message> dmUser(Message message) {
Message dmMessage = createDmMessage(message);
return message.getAuthor()
.openPrivateChannel()
.flatMap(privateChannel -> privateChannel.sendMessage(dmMessage));
}

@Nonnull
private Message createDmMessage(Message originalMessage) {
String contentRaw = originalMessage.getContentRaw();
String blacklistedAttachments =
String.join(", ", getBlacklistedAttachmentsFromMessage(originalMessage));

String dmMessageContent =
"""
Hey there, you posted a message containing a blacklisted file attachment: %s.
We had to delete your message for security reasons.

Feel free to repost your message without, or with a different file instead. Sorry for any inconvenience caused by this 🙇️
"""
.formatted(blacklistedAttachments);

// No embed needed if there was no message from the user
if (contentRaw.isEmpty()) {
return new MessageBuilder(dmMessageContent).build();
}
return createBaseResponse(contentRaw, dmMessageContent);
}

@Nonnull
private Message createBaseResponse(String originalMessageContent, String dmMessageContent) {
MessageEmbed originalMessageEmbed =
new EmbedBuilder().setDescription(originalMessageContent)
.setColor(Color.ORANGE)
.build();
return new MessageBuilder(dmMessageContent).setEmbeds(originalMessageEmbed).build();
}

@Nonnull
private List<String> getBlacklistedAttachmentsFromMessage(Message originalMessage) {
return originalMessage.getAttachments()
.stream()
.filter(attachment -> blacklistedFileExtensions
.contains(attachment.getFileExtension().toLowerCase(Locale.US)))
.map(Message.Attachment::getFileName)
.toList();
}

private boolean doesMessageContainBlacklistedContent(Message message) {
return message.getAttachments()
.stream()
.anyMatch(attachment -> blacklistedFileExtensions
.contains(attachment.getFileExtension().toLowerCase(Locale.US)));
}

private void warnMods(Message sentUserMessage) {
String blacklistedAttachmentsFromMessage =
String.join(", ", getBlacklistedAttachmentsFromMessage(sentUserMessage));

modAuditLogWriter.write(
"Message with blacklisted content detected: %s"
.formatted(blacklistedAttachmentsFromMessage),
"Sent Message: %s".formatted(sentUserMessage), sentUserMessage.getAuthor(),
sentUserMessage.getTimeCreated(), sentUserMessage.getGuild());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

/**
* Configuration of the application. Create instances using {@link #load(Path)}.
Expand All @@ -28,6 +30,7 @@ public final class Config {
private final ScamBlockerConfig scamBlocker;
private final String wolframAlphaAppId;
private final HelpSystemConfig helpSystem;
private final List<String> blacklistedFileExtension;

private final String mediaOnlyChannelPattern;

Expand All @@ -48,7 +51,8 @@ private Config(@JsonProperty("token") String token,
@JsonProperty("scamBlocker") ScamBlockerConfig scamBlocker,
@JsonProperty("wolframAlphaAppId") String wolframAlphaAppId,
@JsonProperty("helpSystem") HelpSystemConfig helpSystem,
@JsonProperty("mediaOnlyChannelPattern") String mediaOnlyChannelPattern) {
@JsonProperty("mediaOnlyChannelPattern") String mediaOnlyChannelPattern,
@JsonProperty("blacklistedFileExtension") List<String> blacklistedFileExtension) {
this.token = token;
this.gistApiKey = gistApiKey;
this.databasePath = databasePath;
Expand All @@ -65,6 +69,7 @@ private Config(@JsonProperty("token") String token,
this.wolframAlphaAppId = wolframAlphaAppId;
this.helpSystem = helpSystem;
this.mediaOnlyChannelPattern = mediaOnlyChannelPattern;
this.blacklistedFileExtension = blacklistedFileExtension;
}

/**
Expand Down Expand Up @@ -235,4 +240,14 @@ public HelpSystemConfig getHelpSystem() {
public String getMediaOnlyChannelPattern() {
return mediaOnlyChannelPattern;
}

/**
* Gets a list of all blacklisted file extensions.
*
* @return a list of all blacklisted file extensions
*/
@Nonnull
public List<String> getBlacklistedFileExtensions() {
return Collections.unmodifiableList(blacklistedFileExtension);
}
}