Refactoring, ChromaBot API, fixes, needs testing

This commit is contained in:
Norbi Peti 2017-11-29 22:30:34 +01:00
parent 20ff9cc3e6
commit 4c8a1c4582
8 changed files with 240 additions and 113 deletions

View file

@ -1,11 +1,131 @@
package buttondevteam.discordplugin;
import java.awt.Color;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitScheduler;
import buttondevteam.discordplugin.listeners.MCChatListener;
import lombok.Getter;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.util.EmbedBuilder;
public class ChromaBot {
/**
* May be null if it's not initialized
* May be null if it's not initialized. Initialization happens after the server is done loading (using {@link BukkitScheduler#runTaskAsynchronously(org.bukkit.plugin.Plugin, Runnable)})
*/
public @Getter ChromaBot instance;
// TODO
private static @Getter ChromaBot instance;
private DiscordPlugin dp;
/**
* This will set the instance field.
*
* @param dp
* The Discord plugin
*/
ChromaBot(DiscordPlugin dp) {
instance = this;
this.dp = dp;
}
static void delete() {
instance = null;
}
/**
* Send a message to the chat channel and private chats.
*
* @param message
* The message to send, duh
*/
public void sendMessage(String message) {
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message));
}
/**
* Send a message to the chat channel and private chats.
*
* @param message
* The message to send, duh
* @param embed
* Custom fancy stuff, use {@link EmbedBuilder} to create one
*/
public void sendMessage(String message, EmbedObject embed) {
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, embed));
}
/**
* Send a message to an arbitrary channel. This will not send it to the private chats.
*
* @param channel
* The channel to send to, use the channel variables in {@link DiscordPlugin}
* @param message
* The message to send, duh
* @param embed
* Custom fancy stuff, use {@link EmbedBuilder} to create one
*/
public void sendMessage(IChannel channel, String message, EmbedObject embed) {
DiscordPlugin.sendMessageToChannel(channel, message, embed);
}
/**
* Send a fancy message to the chat channel. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
*/
public void sendFancyMessage(String message, Color color) {
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message,
new EmbedBuilder().withTitle(message).withColor(color).build()));
}
/**
* Send a fancy message to the chat channel. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
* @param mcauthor
* The name of the Minecraft player who is the author of this message
*/
public void sendFancyMessage(String message, Color color, String mcauthor) {
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message,
DPUtils.embedWithHead(new EmbedBuilder().withTitle(message).withColor(color), mcauthor).build()));
}
/**
* Send a fancy message to the chat channel. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
* @param authorname
* The name of the author of this message
* @param authorimg
* The URL of the avatar image for this message's author
*/
public void sendFancyMessage(String message, Color color, String authorname, String authorimg) {
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, new EmbedBuilder()
.withTitle(message).withColor(color).withAuthorName(authorname).withAuthorIcon(authorimg).build()));
}
public void updatePlayerList() {
DPUtils.performNoWait(() -> {
String[] s = DiscordPlugin.chatchannel.getTopic().split("\\n----\\n");
if (s.length < 3)
return;
s[0] = Bukkit.getOnlinePlayers().size() + " player" + (Bukkit.getOnlinePlayers().size() != 1 ? "s" : "")
+ " online";
s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream()
.map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", "));
DiscordPlugin.chatchannel.changeTopic(Arrays.stream(s).collect(Collectors.joining("\n----\n")));
});
}
}

View file

@ -0,0 +1,67 @@
package buttondevteam.discordplugin;
import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.RequestBuffer;
import sx.blah.discord.util.RequestBuffer.IRequest;
import sx.blah.discord.util.RequestBuffer.IVoidRequest;
public final class DPUtils {
public static EmbedBuilder embedWithHead(EmbedBuilder builder, String playername) {
return builder.withAuthorIcon("https://minotar.net/avatar/" + playername + "/32.png");
}
/** Removes §[char] colour codes from strings */
public static String sanitizeString(String string) {
String sanitizedString = "";
boolean random = false;
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == '§') {
i++;// Skips the data value, the 4 in "§4Alisolarflare"
if (string.charAt(i) == 'k')
random = true;
else
random = false;
} else {
if (!random) // Skip random/obfuscated characters
sanitizedString += string.charAt(i);
}
}
return sanitizedString;
}
/**
* Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode.
*/
public static <T> T perform(IRequest<T> action) {
if (DiscordPlugin.SafeMode)
return null;
if (Thread.currentThread() == DiscordPlugin.mainThread)
throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
/**
* Performs Discord actions, retrying when ratelimited.
*/
public static Void perform(IVoidRequest action) {
if (DiscordPlugin.SafeMode)
return null;
if (Thread.currentThread() == DiscordPlugin.mainThread)
throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
public static void performNoWait(IVoidRequest action) {
if (DiscordPlugin.SafeMode)
return;
RequestBuffer.request(action);
}
public static <T> void performNoWait(IRequest<T> action) {
if (DiscordPlugin.SafeMode)
return;
RequestBuffer.request(action);
}
}

View file

@ -4,11 +4,9 @@ import java.awt.Color;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.event.player.PlayerQuitEvent;
@ -33,13 +31,11 @@ import sx.blah.discord.handle.impl.events.ReadyEvent;
import sx.blah.discord.handle.impl.obj.ReactionEmoji;
import sx.blah.discord.handle.obj.*;
import sx.blah.discord.util.*;
import sx.blah.discord.util.RequestBuffer.IRequest;
import sx.blah.discord.util.RequestBuffer.IVoidRequest;
public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
private static final String SubredditURL = "https://www.reddit.com/r/ChromaGamers";
private static boolean stop = false;
private static Thread mainThread;
static Thread mainThread;
public static IDiscordClient dc;
public static DiscordPlugin plugin;
public static boolean SafeMode = true;
@ -141,7 +137,7 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
.withTitle("Server started - chat connected.").build());
getConfig().set("serverup", true);
saveConfig();
performNoWait(() -> {
DPUtils.performNoWait(() -> {
try {
List<IMessage> msgs = genchannel.getPinnedMessages();
for (int i = msgs.size() - 1; i >= 10; i--) { // Unpin all pinned messages except the newest 10
@ -172,7 +168,7 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
"You could make a religion out of this");
}
}
updatePlayerList();
new ChromaBot(this).updatePlayerList();
}
}, 0, 10);
for (IListener<?> listener : CommandListener.getListeners())
@ -218,6 +214,7 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
sendMessageToChannel(chatchannel, "", new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED)
.withTitle(Restart ? "Server restarting" : "Server stopping").build());
try {
ChromaBot.delete();
dc.online("on TBMC");
dc.logout();
} catch (Exception e) {
@ -326,9 +323,9 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
RequestBuffer.IRequest<IMessage> r = () -> embed == null ? channel.sendMessage(content)
: channel.sendMessage(content, embed, false);
if (wait)
return perform(r);
return DPUtils.perform(r);
else {
performNoWait(r);
DPUtils.performNoWait(r);
return null;
}
} catch (Exception e) {
@ -338,10 +335,6 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
}
}
public static EmbedBuilder embedWithHead(EmbedBuilder builder, String playername) {
return builder.withAuthorIcon("https://minotar.net/avatar/" + playername + "/32.png");
}
public static Permission perms;
public boolean setupProviders() {
@ -357,70 +350,4 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
perms = permsProvider.getProvider();
return perms != null;
}
/** Removes §[char] colour codes from strings */
public static String sanitizeString(String string) {
String sanitizedString = "";
boolean random = false;
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == '§') {
i++;// Skips the data value, the 4 in "§4Alisolarflare"
if (string.charAt(i) == 'k')
random = true;
else
random = false;
} else {
if (!random) // Skip random/obfuscated characters
sanitizedString += string.charAt(i);
}
}
return sanitizedString;
}
/**
* Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode.
*/
public static <T> T perform(IRequest<T> action) {
if (SafeMode)
return null;
if (Thread.currentThread() == mainThread)
throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
/**
* Performs Discord actions, retrying when ratelimited.
*/
public static Void perform(IVoidRequest action) {
if (SafeMode)
return null;
if (Thread.currentThread() == mainThread)
throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
public static void performNoWait(IVoidRequest action) {
if (SafeMode)
return;
RequestBuffer.request(action);
}
public static <T> void performNoWait(IRequest<T> action) {
if (SafeMode)
return;
RequestBuffer.request(action);
}
public static void updatePlayerList() {
performNoWait(() -> {
String[] s = chatchannel.getTopic().split("\\n----\\n");
if (s.length < 3)
return;
s[0] = Bukkit.getOnlinePlayers().size() + " player" + (Bukkit.getOnlinePlayers().size() != 1 ? "s" : "")
+ " online";
s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream()
.map(p -> DiscordPlugin.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", "));
chatchannel.changeTopic(Arrays.stream(s).collect(Collectors.joining("\n----\n")));
});
}
}

View file

@ -50,7 +50,7 @@ public abstract class DiscordSenderBase implements IDiscordSender {
final boolean broadcast = new Exception().getStackTrace()[2].getMethodName().contains("broadcast");
if (broadcast)
return;
final String sendmsg = DiscordPlugin.sanitizeString(message);
final String sendmsg = DPUtils.sanitizeString(message);
msgtosend += "\n" + sendmsg;
if (sendtask == null)
sendtask = Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> {

View file

@ -3,6 +3,7 @@ package buttondevteam.discordplugin.commands;
import java.util.List;
import java.util.stream.Collectors;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import sx.blah.discord.handle.obj.IMessage;
@ -28,7 +29,7 @@ public class RoleCommand extends DiscordCommandBase {
if (role == null)
return;
try {
DiscordPlugin.perform(() -> message.getAuthor().addRole(role));
DPUtils.perform(() -> message.getAuthor().addRole(role));
DiscordPlugin.sendMessageToChannel(message.getChannel(), "Added game role.");
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while adding role!", e);
@ -39,7 +40,7 @@ public class RoleCommand extends DiscordCommandBase {
if (role == null)
return;
try {
DiscordPlugin.perform(() -> message.getAuthor().removeRole(role));
DPUtils.perform(() -> message.getAuthor().removeRole(role));
DiscordPlugin.sendMessageToChannel(message.getChannel(), "Removed game role.");
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while removing role!", e);

View file

@ -3,6 +3,7 @@ package buttondevteam.discordplugin.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.PluginUpdater;
import buttondevteam.lib.TBMCCoreAPI;
@ -13,7 +14,7 @@ public class AutoUpdaterListener implements Listener {
if (DiscordPlugin.SafeMode)
return;
try {
DiscordPlugin.performNoWait(() -> DiscordPlugin.officechannel.getMessageHistory(10).stream()
DPUtils.performNoWait(() -> DiscordPlugin.officechannel.getMessageHistory(10).stream()
.filter(m -> m.getWebhookLongID() == 239123781401051138L && m.getEmbeds().get(0).getTitle()
.contains(event.getData().get("repository").getAsJsonObject().get("name").getAsString()))
.findFirst().get().addReaction(DiscordPlugin.DELIVERED_REACTION));

View file

@ -35,7 +35,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
return;
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
synchronized (this) {
final String authorPlayer = DiscordPlugin.sanitizeString(e.getSender() instanceof Player //
final String authorPlayer = DPUtils.sanitizeString(e.getSender() instanceof Player //
? ((Player) e.getSender()).getDisplayName() //
: e.getSender().getName());
final EmbedBuilder embed = new EmbedBuilder().withAuthorName(authorPlayer)
@ -44,8 +44,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
// embed.appendField("Channel", ((e.getSender() instanceof DiscordSenderBase ? "d|" : "")
// + DiscordPlugin.sanitizeString(e.getChannel().DisplayName)), false);
if (e.getSender() instanceof Player)
DiscordPlugin
.embedWithHead(
DPUtils.embedWithHead(
embed.withAuthorUrl("https://tbmcplugins.github.io/profile.html?type=minecraft&id="
+ ((Player) e.getSender()).getUniqueId()),
((Player) e.getSender()).getName());
@ -59,13 +58,14 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
Consumer<LastMsgData> doit = lastmsgdata -> {
final EmbedObject embedObject = embed.build();
final String dmsg = lastmsgdata.channel.isPrivate()
? DiscordPlugin.sanitizeString(e.getChannel().DisplayName) : "";
? DPUtils.sanitizeString(e.getChannel().DisplayName)
: "";
if (lastmsgdata.message == null || lastmsgdata.message.isDeleted()
|| !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName())
|| lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120
|| !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) {
lastmsgdata.message = DiscordPlugin.sendMessageToChannelWait(lastmsgdata.channel, dmsg,
embedObject);
embedObject); // TODO Use ChromaBot API
lastmsgdata.time = nanoTime;
lastmsgdata.mcchannel = e.getChannel();
lastmsgdata.content = embedObject.description;
@ -74,7 +74,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
lastmsgdata.content = embedObject.description = lastmsgdata.content + "\n"
+ embedObject.description;// The message object doesn't get updated
final LastMsgData _lastmsgdata = lastmsgdata;
DiscordPlugin.perform(() -> _lastmsgdata.message.edit(dmsg, embedObject));
DPUtils.perform(() -> _lastmsgdata.message.edit(dmsg, embedObject));
} catch (MissingPermissionsException | DiscordException e1) {
TBMCCoreAPI.SendException("An error occured while editing chat message!", e1);
}
@ -85,8 +85,9 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
if ((e.getChannel() == Channel.GlobalChat || e.getChannel().ID.equals("rp"))
&& isdifferentchannel.test(DiscordPlugin.chatchannel))
doit.accept(lastmsgdata == null
? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel, null, null) : lastmsgdata);
doit.accept(
lastmsgdata == null ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel, null, null)
: lastmsgdata);
for (LastMsgData data : lastmsgPerUser) {
if (data.dp.isMinecraftChatEnabled() && isdifferentchannel.test(data.channel)
@ -123,8 +124,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
DiscordPlugin.dc.getUsersByName(event.getMessage().substring(start + 1, mid)).stream()
.filter(u -> u.getDiscriminator().equals(event.getMessage().substring(mid + 1, end))).findAny()
.ifPresent(user -> event.setMessage(event.getMessage().substring(0, startF) + "@" + user.getName()
+ (event.getMessage().length() > end ? event.getMessage().substring(end)
: ""))); // TODO: Add formatting
+ (event.getMessage().length() > end ? event.getMessage().substring(end) : ""))); // TODO: Add formatting
start = end; // Skip any @s inside the mention
}
}
@ -203,18 +203,26 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
* This overload sends it to the global chat.
*/
public static void sendSystemMessageToChat(String msg) {
DiscordPlugin.sendMessageToChannel(DiscordPlugin.chatchannel, DiscordPlugin.sanitizeString(msg));
for (LastMsgData data : lastmsgPerUser)
DiscordPlugin.sendMessageToChannel(data.channel, DiscordPlugin.sanitizeString(msg));
forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, DPUtils.sanitizeString(msg)));
}
public static void sendSystemMessageToChat(TBMCSystemChatEvent event) {
forAllowedMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, DPUtils.sanitizeString(event.getMessage())),
event);
}
public static void forAllMCChat(Consumer<IChannel> action) {
action.accept(DiscordPlugin.chatchannel);
for (LastMsgData data : lastmsgPerUser)
action.accept(data.channel);
}
private static void forAllowedMCChat(Consumer<IChannel> action, TBMCSystemChatEvent event) {
if (Channel.GlobalChat.ID.equals(event.getChannel().ID))
DiscordPlugin.sendMessageToChannel(DiscordPlugin.chatchannel,
DiscordPlugin.sanitizeString(event.getMessage()));
action.accept(DiscordPlugin.chatchannel);
for (LastMsgData data : lastmsgPerUser)
if (event.shouldSendTo(getSender(data.channel, data.user, data.dp)))
DiscordPlugin.sendMessageToChannel(data.channel, DiscordPlugin.sanitizeString(event.getMessage()));
action.accept(data.channel);
}
@Override // Discord
@ -257,7 +265,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
boolean react = false;
if (dmessage.startsWith("/")) { // Ingame command
DiscordPlugin.perform(() -> {
DPUtils.perform(() -> {
if (!event.getMessage().isDeleted() && !event.getChannel().isPrivate())
event.getMessage().delete();
});
@ -308,7 +316,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
} else
dsender.setMcchannel(Channel.GlobalChat);
dsender.sendMessage("You're now talking in: "
+ DiscordPlugin.sanitizeString(dsender.getMcchannel().DisplayName));
+ DPUtils.sanitizeString(dsender.getMcchannel().DisplayName));
} else { // Send single message
sendChatMessage.accept(chc, cmd.substring(spi + 1));
react = true;
@ -333,12 +341,12 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
try {
final IReaction reaction = m.getReactionByEmoji(DiscordPlugin.DELIVERED_REACTION);
if (reaction != null)
DiscordPlugin.perform(() -> m.removeReaction(DiscordPlugin.dc.getOurUser(), reaction));
DPUtils.perform(() -> m.removeReaction(DiscordPlugin.dc.getOurUser(), reaction));
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e);
}
});
DiscordPlugin.performNoWait(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION));
DPUtils.performNoWait(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION));
}
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e);

View file

@ -23,6 +23,8 @@ import org.bukkit.plugin.RegisteredListener;
import com.earth2me.essentials.CommandSource;
import buttondevteam.discordplugin.ChromaBot;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlayerSender;
@ -71,7 +73,7 @@ public class MCListener implements Listener {
if (!DiscordPlugin.hooked)
MCChatListener.sendSystemMessageToChat(e.GetPlayer().PlayerName().get() + " joined the game");
MCChatListener.ListC = 0;
DiscordPlugin.updatePlayerList();
ChromaBot.getInstance().updatePlayerList();
}
@EventHandler(priority = EventPriority.HIGHEST)
@ -86,7 +88,8 @@ public class MCListener implements Listener {
.ifPresent(dcp -> callEventExcludingSome(new PlayerJoinEvent(dcp, ""))));
if (!DiscordPlugin.hooked)
MCChatListener.sendSystemMessageToChat(e.GetPlayer().PlayerName().get() + " left the game");
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, DiscordPlugin::updatePlayerList, 5);
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
ChromaBot.getInstance()::updatePlayerList, 5);
}
@EventHandler
@ -115,7 +118,7 @@ public class MCListener implements Listener {
public void onPlayerAFK(AfkStatusChangeEvent e) {
if (e.isCancelled() || !e.getAffected().getBase().isOnline())
return;
MCChatListener.sendSystemMessageToChat(DiscordPlugin.sanitizeString(e.getAffected().getBase().getDisplayName())
MCChatListener.sendSystemMessageToChat(DPUtils.sanitizeString(e.getAffected().getBase().getDisplayName())
+ " is " + (e.getValue() ? "now" : "no longer") + " AFK.");
}
@ -127,7 +130,7 @@ public class MCListener implements Listener {
@EventHandler
public void onPlayerMute(MuteStatusChangeEvent e) {
try {
DiscordPlugin.performNoWait(() -> {
DPUtils.performNoWait(() -> {
final IRole role = DiscordPlugin.dc.getRoleByID(164090010461667328L);
final CommandSource source = e.getAffected().getSource();
if (!source.isPlayer())