Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace f*cks with hugs #308

Merged
merged 8 commits into from
Jul 21, 2022
Merged
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ dependencies {

// Quartz scheduler
implementation("org.quartz-scheduler:quartz:2.3.2")

// Webhooks
implementation("club.minnced:discord-webhooks:0.8.0")

// Lombok Annotations
compileOnly("org.projectlombok:lombok:1.18.24")
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/net/javadiscord/javabot/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ private static void addEventListeners(JDA jda) {
new HelpChannelListener(),
new ShareKnowledgeVoteListener(),
new JobChannelVoteListener(),
new PingableNameListener()
new PingableNameListener(),
new HugListener()
);
}
}
Expand Down
76 changes: 76 additions & 0 deletions src/main/java/net/javadiscord/javabot/listener/HugListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package net.javadiscord.javabot.listener;

import javax.annotation.Nonnull;

import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.GuildMessageChannel;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.Webhook;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.javadiscord.javabot.Bot;
import net.javadiscord.javabot.util.WebhookUtil;

/**
* Replaces all occurences of 'fuck' in incoming messages with 'hug'.
*/
@Slf4j
public class HugListener extends ListenerAdapter {
@Override
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
if (!event.isFromGuild()) {
return;
}
if (Bot.autoMod.hasSuspiciousLink(event.getMessage()) || Bot.autoMod.hasAdvertisingLink(event.getMessage())) {
return;
}
if (!event.getMessage().getMentions().getUsers().isEmpty()) {
return;
}
if (event.isWebhookMessage()) {
return;
}
TextChannel tc = null;
if (event.isFromType(ChannelType.TEXT)) {
tc = event.getTextChannel();
}
if (event.isFromThread()) {
GuildMessageChannel parentChannel = event.getThreadChannel().getParentMessageChannel();
if (parentChannel instanceof TextChannel textChannel) {
tc = textChannel;
}
}
if (tc == null) {
return;
}
final TextChannel textChannel = tc;
String content = event.getMessage().getContentRaw();
String lowerCaseContent = content.toLowerCase();
if (lowerCaseContent.contains("fuck")) {
long threadId = event.isFromThread() ? event.getThreadChannel().getIdLong() : 0;
StringBuilder sb = new StringBuilder(content.length());
int index = 0;
int indexBkp = index;
while ((index = lowerCaseContent.indexOf("fuck", index)) != -1) {
sb.append(content.substring(indexBkp, index));
sb.append("hug");
indexBkp = index++ + 4;
}

sb.append(content.substring(indexBkp, content.length()));
WebhookUtil.ensureWebhookExists(textChannel,
wh -> sendWebhookMessage(wh, event.getMessage(), sb.toString(), threadId),
e -> log.error("Webhook lookup/creation failed", e));
}
}

private void sendWebhookMessage(Webhook webhook, Message originalMessage, String newMessageContent, long threadId) {
WebhookUtil.mirrorMessageToWebhook(webhook, originalMessage, newMessageContent, threadId)
.thenAccept(unused -> originalMessage.delete().queue()).exceptionally(e -> {
log.error("replacing the content 'fuck' with 'hug' in an incoming message failed", e);
return null;
});
}
}
93 changes: 93 additions & 0 deletions src/main/java/net/javadiscord/javabot/util/WebhookUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package net.javadiscord.javabot.util;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import club.minnced.discord.webhook.WebhookClientBuilder;
import club.minnced.discord.webhook.external.JDAWebhookClient;
import club.minnced.discord.webhook.send.AllowedMentions;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.Webhook;
import net.dv8tion.jda.api.entities.Message.Attachment;

/**
* Contains utility methods for dealing with Discord Webhooks.
*/
public class WebhookUtil {
private WebhookUtil() {
}

/**
* Makes sure that a writable webhook exists in a specific channel. if no
* suitable webhook is found, one is created.
*
* @param channel the {@link TextChannel} the webhook should exist in
* @param callback an action that is executed once a webhook is
* found/created
*/
public static void ensureWebhookExists(TextChannel channel, Consumer<? super Webhook> callback) {
ensureWebhookExists(channel, callback, err -> {
});
}

/**
* Makes sure that a writable webhook exists in a specific channel. if no
* suitable webhook is found, one is created.
*
* @param channel the {@link TextChannel} the webhook should exist in
* @param callback an action that is executed once a webhook is
* found/created
* @param failureCallback an action that is executed if the webhook
* lookup/creation failed
*/
public static void ensureWebhookExists(TextChannel channel, Consumer<? super Webhook> callback,
Consumer<? super Throwable> failureCallback) {

channel.retrieveWebhooks().queue(webhooks -> {
Optional<Webhook> hook = webhooks.stream()
.filter(webhook -> webhook.getChannel().getIdLong() == channel.getIdLong())
.filter(wh -> wh.getToken() != null).findAny();
if (hook.isPresent()) {
callback.accept(hook.get());
} else {
channel.createWebhook("JavaBot-webhook").queue(callback, failureCallback);
}
}, failureCallback);
}

/**
* Resends a specific message using a webhook with a custom content.
*
* @param webhook the webhook used for sending the message
* @param originalMessage the message to copy
* @param newMessageContent the new (custom) content
* @param threadId the thread to send the message in or {@code 0} if the
* message should be sent directly
* @return a {@link CompletableFuture} representing the action of sending
* the message
*/
public static CompletableFuture<Void> mirrorMessageToWebhook(Webhook webhook, Message originalMessage,
String newMessageContent, long threadId) {
JDAWebhookClient client = new WebhookClientBuilder(webhook.getIdLong(), webhook.getToken())
.setThreadId(threadId).buildJDA();
WebhookMessageBuilder message = new WebhookMessageBuilder().setContent(newMessageContent)
.setAllowedMentions(AllowedMentions.none())
.setAvatarUrl(originalMessage.getMember().getEffectiveAvatarUrl())
.setUsername(originalMessage.getMember().getEffectiveName());

List<Attachment> attachments = originalMessage.getAttachments();
@SuppressWarnings("unchecked")
CompletableFuture<?>[] futures = new CompletableFuture<?>[attachments.size()];
for (int i = 0; i < attachments.size(); i++) {
Attachment attachment = attachments.get(i);
futures[i] = attachment.getProxy().download().thenAccept(
is -> message.addFile((attachment.isSpoiler() ? "SPOILER_" : "") + attachment.getFileName(), is));
}
return CompletableFuture.allOf(futures).thenAccept(unused -> client.send(message.build()))
.whenComplete((result, err) -> client.close());
}
}