Skip to content

Commit ef4af86

Browse files
Merge pull request #308 from danthe1st/hugs
replace f*cks with hugs
2 parents 85f579f + 05beef6 commit ef4af86

File tree

4 files changed

+198
-1
lines changed

4 files changed

+198
-1
lines changed

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ dependencies {
3737

3838
// Quartz scheduler
3939
implementation("org.quartz-scheduler:quartz:2.3.2")
40+
41+
// Webhooks
42+
implementation("club.minnced:discord-webhooks:0.8.0")
4043

4144
// Lombok Annotations
4245
compileOnly("org.projectlombok:lombok:1.18.24")

src/main/java/net/javadiscord/javabot/Bot.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ private static void addEventListeners(JDA jda) {
142142
new HelpChannelListener(),
143143
new ShareKnowledgeVoteListener(),
144144
new JobChannelVoteListener(),
145-
new PingableNameListener()
145+
new PingableNameListener(),
146+
new HugListener()
146147
);
147148
}
148149
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package net.javadiscord.javabot.listener;
2+
3+
import javax.annotation.Nonnull;
4+
5+
import lombok.extern.slf4j.Slf4j;
6+
import net.dv8tion.jda.api.entities.ChannelType;
7+
import net.dv8tion.jda.api.entities.GuildMessageChannel;
8+
import net.dv8tion.jda.api.entities.Message;
9+
import net.dv8tion.jda.api.entities.TextChannel;
10+
import net.dv8tion.jda.api.entities.Webhook;
11+
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
12+
import net.dv8tion.jda.api.hooks.ListenerAdapter;
13+
import net.javadiscord.javabot.Bot;
14+
import net.javadiscord.javabot.util.WebhookUtil;
15+
16+
/**
17+
* Replaces all occurences of 'fuck' in incoming messages with 'hug'.
18+
*/
19+
@Slf4j
20+
public class HugListener extends ListenerAdapter {
21+
@Override
22+
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
23+
if (!event.isFromGuild()) {
24+
return;
25+
}
26+
if (Bot.autoMod.hasSuspiciousLink(event.getMessage()) || Bot.autoMod.hasAdvertisingLink(event.getMessage())) {
27+
return;
28+
}
29+
if (!event.getMessage().getMentions().getUsers().isEmpty()) {
30+
return;
31+
}
32+
if (event.isWebhookMessage()) {
33+
return;
34+
}
35+
if (event.getChannel().getIdLong() == Bot.config.get(event.getGuild()).getModeration()
36+
.getSuggestionChannelId()) {
37+
return;
38+
}
39+
TextChannel tc = null;
40+
if (event.isFromType(ChannelType.TEXT)) {
41+
tc = event.getTextChannel();
42+
}
43+
if (event.isFromThread()) {
44+
GuildMessageChannel parentChannel = event.getThreadChannel().getParentMessageChannel();
45+
if (parentChannel instanceof TextChannel textChannel) {
46+
tc = textChannel;
47+
}
48+
}
49+
if (tc == null) {
50+
return;
51+
}
52+
final TextChannel textChannel = tc;
53+
String content = event.getMessage().getContentRaw();
54+
String lowerCaseContent = content.toLowerCase();
55+
if (lowerCaseContent.contains("fuck")) {
56+
long threadId = event.isFromThread() ? event.getThreadChannel().getIdLong() : 0;
57+
StringBuilder sb = new StringBuilder(content.length());
58+
int index = 0;
59+
int indexBkp = index;
60+
while ((index = lowerCaseContent.indexOf("fuck", index)) != -1) {
61+
sb.append(content.substring(indexBkp, index));
62+
sb.append(loadHug(content, index));
63+
indexBkp = index++ + 4;
64+
if (content.length() >= indexBkp + 3 && "ing".equals(lowerCaseContent.substring(indexBkp, indexBkp + 3))) {
65+
sb.append(copyCase(content, indexBkp-1, 'g'));
66+
sb.append(content.substring(indexBkp, indexBkp + 3));
67+
index+=3;
68+
indexBkp+=3;
69+
}
70+
}
71+
72+
sb.append(content.substring(indexBkp, content.length()));
73+
WebhookUtil.ensureWebhookExists(textChannel,
74+
wh -> sendWebhookMessage(wh, event.getMessage(), sb.toString(), threadId),
75+
e -> log.error("Webhook lookup/creation failed", e));
76+
}
77+
}
78+
79+
private String loadHug(String originalText, int startIndex) {
80+
return copyCase(originalText, startIndex, 'h') + ""
81+
+ copyCase(originalText, startIndex + 1, 'u') + ""
82+
+ copyCase(originalText, startIndex + 3, 'g');
83+
}
84+
85+
private char copyCase(String original, int index, char newChar) {
86+
if (Character.isUpperCase(original.charAt(index))) {
87+
return Character.toUpperCase(newChar);
88+
} else {
89+
return newChar;
90+
}
91+
}
92+
93+
private void sendWebhookMessage(Webhook webhook, Message originalMessage, String newMessageContent, long threadId) {
94+
WebhookUtil.mirrorMessageToWebhook(webhook, originalMessage, newMessageContent, threadId)
95+
.thenAccept(unused -> originalMessage.delete().queue()).exceptionally(e -> {
96+
log.error("replacing the content 'fuck' with 'hug' in an incoming message failed", e);
97+
return null;
98+
});
99+
}
100+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package net.javadiscord.javabot.util;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.function.Consumer;
7+
8+
import club.minnced.discord.webhook.WebhookClientBuilder;
9+
import club.minnced.discord.webhook.external.JDAWebhookClient;
10+
import club.minnced.discord.webhook.send.AllowedMentions;
11+
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
12+
import net.dv8tion.jda.api.entities.Message;
13+
import net.dv8tion.jda.api.entities.TextChannel;
14+
import net.dv8tion.jda.api.entities.Webhook;
15+
import net.dv8tion.jda.api.entities.Message.Attachment;
16+
17+
/**
18+
* Contains utility methods for dealing with Discord Webhooks.
19+
*/
20+
public class WebhookUtil {
21+
private WebhookUtil() {
22+
}
23+
24+
/**
25+
* Makes sure that a writable webhook exists in a specific channel. if no
26+
* suitable webhook is found, one is created.
27+
*
28+
* @param channel the {@link TextChannel} the webhook should exist in
29+
* @param callback an action that is executed once a webhook is
30+
* found/created
31+
*/
32+
public static void ensureWebhookExists(TextChannel channel, Consumer<? super Webhook> callback) {
33+
ensureWebhookExists(channel, callback, err -> {
34+
});
35+
}
36+
37+
/**
38+
* Makes sure that a writable webhook exists in a specific channel. if no
39+
* suitable webhook is found, one is created.
40+
*
41+
* @param channel the {@link TextChannel} the webhook should exist in
42+
* @param callback an action that is executed once a webhook is
43+
* found/created
44+
* @param failureCallback an action that is executed if the webhook
45+
* lookup/creation failed
46+
*/
47+
public static void ensureWebhookExists(TextChannel channel, Consumer<? super Webhook> callback,
48+
Consumer<? super Throwable> failureCallback) {
49+
50+
channel.retrieveWebhooks().queue(webhooks -> {
51+
Optional<Webhook> hook = webhooks.stream()
52+
.filter(webhook -> webhook.getChannel().getIdLong() == channel.getIdLong())
53+
.filter(wh -> wh.getToken() != null).findAny();
54+
if (hook.isPresent()) {
55+
callback.accept(hook.get());
56+
} else {
57+
channel.createWebhook("JavaBot-webhook").queue(callback, failureCallback);
58+
}
59+
}, failureCallback);
60+
}
61+
62+
/**
63+
* Resends a specific message using a webhook with a custom content.
64+
*
65+
* @param webhook the webhook used for sending the message
66+
* @param originalMessage the message to copy
67+
* @param newMessageContent the new (custom) content
68+
* @param threadId the thread to send the message in or {@code 0} if the
69+
* message should be sent directly
70+
* @return a {@link CompletableFuture} representing the action of sending
71+
* the message
72+
*/
73+
public static CompletableFuture<Void> mirrorMessageToWebhook(Webhook webhook, Message originalMessage,
74+
String newMessageContent, long threadId) {
75+
JDAWebhookClient client = new WebhookClientBuilder(webhook.getIdLong(), webhook.getToken())
76+
.setThreadId(threadId).buildJDA();
77+
WebhookMessageBuilder message = new WebhookMessageBuilder().setContent(newMessageContent)
78+
.setAllowedMentions(AllowedMentions.none())
79+
.setAvatarUrl(originalMessage.getMember().getEffectiveAvatarUrl())
80+
.setUsername(originalMessage.getMember().getEffectiveName());
81+
82+
List<Attachment> attachments = originalMessage.getAttachments();
83+
@SuppressWarnings("unchecked")
84+
CompletableFuture<?>[] futures = new CompletableFuture<?>[attachments.size()];
85+
for (int i = 0; i < attachments.size(); i++) {
86+
Attachment attachment = attachments.get(i);
87+
futures[i] = attachment.getProxy().download().thenAccept(
88+
is -> message.addFile((attachment.isSpoiler() ? "SPOILER_" : "") + attachment.getFileName(), is));
89+
}
90+
return CompletableFuture.allOf(futures).thenAccept(unused -> client.send(message.build()))
91+
.whenComplete((result, err) -> client.close());
92+
}
93+
}

0 commit comments

Comments
 (0)