Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
import net.essentialsx.discord.util.ConsoleInjector;
import net.essentialsx.discord.util.DiscordUtil;
import net.essentialsx.discord.util.MessageUtil;
import net.essentialsx.discord.util.WrappedWebhookClient;
import net.essentialsx.discord.util.WebhookDispatcher;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
Expand Down Expand Up @@ -84,11 +84,11 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
private JDA jda;
private Guild guild;
private TextChannel primaryChannel;
private WrappedWebhookClient consoleWebhook;
private WebhookDispatcher consoleWebhook;
private String lastConsoleId;
private final Map<String, MessageType> registeredTypes = new HashMap<>();
private final Map<MessageType, String> typeToChannelId = new HashMap<>();
private final Map<String, WrappedWebhookClient> channelIdToWebhook = new HashMap<>();
private final Map<String, WebhookDispatcher> channelIdToWebhook = new HashMap<>();
private ConsoleInjector injector;
private DiscordCommandDispatcher commandDispatcher;
private InteractionControllerImpl interactionController;
Expand Down Expand Up @@ -130,12 +130,7 @@ public WebhookMessage getWebhookMessage(String message) {
}

public WebhookMessage getWebhookMessage(String message, String avatarUrl, String name, boolean groupMentions) {
return new WebhookMessageBuilder()
.setAvatarUrl(avatarUrl)
.setAllowedMentions(groupMentions ? DiscordUtil.ALL_MENTIONS_WEBHOOK : DiscordUtil.NO_GROUP_MENTIONS_WEBHOOK)
.setUsername(name)
.setContent(message)
.build();
return new WebhookMessageBuilder().setAvatarUrl(avatarUrl).setAllowedMentions(groupMentions ? DiscordUtil.ALL_MENTIONS_WEBHOOK : DiscordUtil.NO_GROUP_MENTIONS_WEBHOOK).setUsername(name).setContent(message).build();
}

public void sendMessage(DiscordMessageEvent event, String message, boolean groupMentions) {
Expand All @@ -145,11 +140,11 @@ public void sendMessage(DiscordMessageEvent event, String message, boolean group

final String webhookChannelId = typeToChannelId.get(event.getType());
if (webhookChannelId != null) {
final WrappedWebhookClient client = channelIdToWebhook.get(webhookChannelId);
if (client != null) {
final WebhookDispatcher dispatcher = channelIdToWebhook.get(webhookChannelId);
if (dispatcher != null) {
final String avatarUrl = event.getAvatarUrl() != null ? event.getAvatarUrl() : jda.getSelfUser().getAvatarUrl();
final String name = event.getName() != null ? event.getName() : guild.getSelfMember().getEffectiveName();
client.send(getWebhookMessage(strippedContent, avatarUrl, name, groupMentions));
dispatcher.send(getWebhookMessage(strippedContent, avatarUrl, name, groupMentions));
return;
}
}
Expand All @@ -158,9 +153,7 @@ public void sendMessage(DiscordMessageEvent event, String message, boolean group
logger.warning(tlLiteral("discordNoSendPermission", channel.getName()));
return;
}
channel.sendMessage(strippedContent)
.setAllowedMentions(groupMentions ? null : DiscordUtil.NO_GROUP_MENTIONS)
.queue();
channel.sendMessage(strippedContent).setAllowedMentions(groupMentions ? null : DiscordUtil.NO_GROUP_MENTIONS).queue(null, error -> logger.log(Level.WARNING, "Failed to send message to channel " + channel.getName(), error));
}

public void startup() throws LoginException, InterruptedException {
Expand All @@ -178,15 +171,7 @@ public void startup() throws LoginException, InterruptedException {
proxySettings.setServer(plugin.getSettings().getHttpProxyServer());
}

jda = JDABuilder.createDefault(plugin.getSettings().getBotToken())
.setWebsocketFactory(wsFactory)
.addEventListeners(new DiscordListener(this))
.enableIntents(GatewayIntent.MESSAGE_CONTENT)
.enableCache(CacheFlag.EMOJI)
.disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
.setContextEnabled(false)
.build()
.awaitReady();
jda = JDABuilder.createDefault(plugin.getSettings().getBotToken()).setWebsocketFactory(wsFactory).addEventListeners(new DiscordListener(this)).enableIntents(GatewayIntent.MESSAGE_CONTENT).enableCache(CacheFlag.EMOJI).disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE).setContextEnabled(false).build().awaitReady();
invalidStartup = false;
updatePresence();
logger.log(Level.INFO, tlLiteral("discordLoggingInDone", jda.getSelfUser().getAsTag()));
Expand All @@ -203,10 +188,7 @@ public void startup() throws LoginException, InterruptedException {
}

final Collection<Permission> requiredPermissions = ImmutableList.of(Permission.MANAGE_WEBHOOKS, Permission.MANAGE_ROLES, Permission.NICKNAME_MANAGE, Permission.VIEW_CHANNEL, Permission.MESSAGE_SEND, Permission.MESSAGE_EMBED_LINKS);
final String[] missingPermissions = requiredPermissions.stream()
.filter(permission -> !guild.getSelfMember().hasPermission(permission))
.map(Permission::getName)
.toArray(String[]::new);
final String[] missingPermissions = requiredPermissions.stream().filter(permission -> !guild.getSelfMember().hasPermission(permission)).map(Permission::getName).toArray(String[]::new);

if (missingPermissions.length > 0) {
invalidStartup = true;
Expand All @@ -229,7 +211,7 @@ public void startup() throws LoginException, InterruptedException {
}

// Load emotes into cache, JDA will handle updates from here on out.
guild.retrieveEmojis().queue();
guild.retrieveEmojis().queue(null, error -> logger.log(Level.WARNING, "Failed to retrieve emojis from guild", error));

updatePrimaryChannel();

Expand Down Expand Up @@ -305,23 +287,11 @@ public void sendChatMessage(final Player player, final String chatMessage) {
public void sendChatMessage(ChatType chatType, Player player, String chatMessage) {
final User user = getPlugin().getEss().getUser(player);

final String formattedMessage = MessageUtil.formatMessage(getSettings().getMcToDiscordFormat(player, chatType),
MessageUtil.sanitizeDiscordMarkdown(player.getName()),
MessageUtil.sanitizeDiscordMarkdown(player.getDisplayName()),
user.isAuthorized("essentials.discord.markdown") ? chatMessage : MessageUtil.sanitizeDiscordMarkdown(chatMessage),
MessageUtil.sanitizeDiscordMarkdown(getPlugin().getEss().getSettings().getWorldAlias(player.getWorld().getName())),
MessageUtil.sanitizeDiscordMarkdown(FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getPrefix(player))),
MessageUtil.sanitizeDiscordMarkdown(FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getSuffix(player))));
final String formattedMessage = MessageUtil.formatMessage(getSettings().getMcToDiscordFormat(player, chatType), MessageUtil.sanitizeDiscordMarkdown(player.getName()), MessageUtil.sanitizeDiscordMarkdown(player.getDisplayName()), user.isAuthorized("essentials.discord.markdown") ? chatMessage : MessageUtil.sanitizeDiscordMarkdown(chatMessage), MessageUtil.sanitizeDiscordMarkdown(getPlugin().getEss().getSettings().getWorldAlias(player.getWorld().getName())), MessageUtil.sanitizeDiscordMarkdown(FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getPrefix(player))), MessageUtil.sanitizeDiscordMarkdown(FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getSuffix(player))));

final String avatarUrl = DiscordUtil.getAvatarUrl(this, player);

final String formattedName = MessageUtil.formatMessage(getSettings().getMcToDiscordNameFormat(player),
player.getName(),
player.getDisplayName(),
getPlugin().getEss().getSettings().getWorldAlias(player.getWorld().getName()),
FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getPrefix(player)),
FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getSuffix(player)),
guild.getMember(jda.getSelfUser()).getEffectiveName());
final String formattedName = MessageUtil.formatMessage(getSettings().getMcToDiscordNameFormat(player), player.getName(), player.getDisplayName(), getPlugin().getEss().getSettings().getWorldAlias(player.getWorld().getName()), FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getPrefix(player)), FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getSuffix(player)), guild.getMember(jda.getSelfUser()).getEffectiveName());

DiscordUtil.dispatchDiscordMessage(this, chatTypeToMessageType(chatType), formattedMessage, user.isAuthorized("essentials.discord.ping"), avatarUrl, formattedName, player.getUniqueId());
}
Expand Down Expand Up @@ -390,8 +360,8 @@ public void updatePresence() {

public void updateTypesRelay() {
if (!getSettings().isShowAvatar() && !getSettings().isCustomBotName()) {
for (WrappedWebhookClient webhook : channelIdToWebhook.values()) {
webhook.close();
for (WebhookDispatcher dispatcher : channelIdToWebhook.values()) {
dispatcher.close();
}
typeToChannelId.clear();
channelIdToWebhook.clear();
Expand All @@ -410,14 +380,14 @@ public void updateTypesRelay() {

final Webhook webhook = DiscordUtil.getOrCreateWebhook(channel, DiscordUtil.ADVANCED_RELAY_NAME).join();
if (webhook == null) {
final WrappedWebhookClient current = channelIdToWebhook.remove(channel.getId());
final WebhookDispatcher current = channelIdToWebhook.remove(channel.getId());
if (current != null) {
current.close();
}
continue;
}
typeToChannelId.put(type, channel.getId());
channelIdToWebhook.put(channel.getId(), DiscordUtil.getWebhookClient(webhook.getIdLong(), webhook.getToken(), jda.getHttpClient()));
channelIdToWebhook.put(channel.getId(), new WebhookDispatcher(DiscordUtil.getWebhookClient(webhook.getIdLong(), webhook.getToken(), jda.getHttpClient())));
}
}

Expand Down Expand Up @@ -471,7 +441,7 @@ public void updateConsoleRelay() {
}

shutdownConsoleRelay(false);
consoleWebhook = DiscordUtil.getWebhookClient(webhookId, webhookToken, jda.getHttpClient());
consoleWebhook = new WebhookDispatcher(DiscordUtil.getWebhookClient(webhookId, webhookToken, jda.getHttpClient()), 100);
if (injector == null || injector.isRemoved()) {
injector = new ConsoleInjector(this);
injector.start();
Expand Down Expand Up @@ -510,8 +480,8 @@ public void shutdown() {

shutdownConsoleRelay(true);

for (WrappedWebhookClient webhook : channelIdToWebhook.values()) {
webhook.close();
for (WebhookDispatcher dispatcher : channelIdToWebhook.values()) {
dispatcher.close();
}

// Unregister leftover jda listeners
Expand Down Expand Up @@ -583,7 +553,10 @@ public CompletableFuture<Void> modifyMemberRoles(InteractionMember member, Colle
}

final CompletableFuture<Void> future = new CompletableFuture<>();
guild.modifyMemberRoles(((InteractionMemberImpl) member).getJdaObject(), add, remove).queue(future::complete);
guild.modifyMemberRoles(((InteractionMemberImpl) member).getJdaObject(), add, remove).queue(future::complete, error -> {
logger.log(Level.WARNING, "Failed to modify member roles", error);
future.complete(null);
});
return future;
}

Expand Down Expand Up @@ -613,7 +586,7 @@ public DiscordSettings getSettings() {
return plugin.getSettings();
}

public WrappedWebhookClient getConsoleWebhook() {
public WebhookDispatcher getConsoleWebhook() {
return consoleWebhook;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
import org.bukkit.Bukkit;

import java.time.Instant;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import static com.earth2me.essentials.I18n.tlLiteral;
Expand All @@ -27,40 +25,18 @@ public class ConsoleInjector extends AbstractAppender {
private final static java.util.logging.Logger logger = EssentialsDiscord.getWrappedLogger();

private final static long QUEUE_PROCESS_PERIOD_SECONDS = 2;
private final static int QUEUE_CAPACITY = 500;

private final JDADiscordService jda;
private final BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
private final int taskId;
private boolean removed = false;

private final AtomicLong lastRateLimitTime = new AtomicLong(0);
private final AtomicInteger recentRateLimit = new AtomicInteger(0);
private final AtomicInteger totalBackoffEvents = new AtomicInteger();

public ConsoleInjector(JDADiscordService jda) {
super("EssentialsX-ConsoleInjector", null, null, false);
this.jda = jda;
((Logger) LogManager.getRootLogger()).addAppender(this);
taskId = Bukkit.getScheduler().runTaskTimerAsynchronously(jda.getPlugin(), () -> {
// Check to see if we're supposed to be backing off, preform backoff if the case.
if (recentRateLimit.get() < 0) {
if (totalBackoffEvents.get() * 20 >= jda.getSettings().getConsoleSkipDelay() * 60) {
logger.warning("EssXBackoff: Reached console skip delay, attempt to skip");
jda.getConsoleWebhook().abandonRequests();
messageQueue.clear();
totalBackoffEvents.set(0);
recentRateLimit.set(0);
lastRateLimitTime.set(0);
return;
}

final int backoff = recentRateLimit.incrementAndGet();
if (jda.isDebug()) {
logger.warning("EssXBackoff: Webhook backoff in progress, skipping queue processing. Resuming in " + Math.abs(backoff) + " cycles.");
}
return;
}

final StringBuilder buffer = new StringBuilder();
String curLine;
while ((curLine = messageQueue.peek()) != null) {
Expand All @@ -78,7 +54,11 @@ public ConsoleInjector(JDADiscordService jda) {
}

private void sendMessage(String content) {
jda.getConsoleWebhook().send(jda.getWebhookMessage(content)).exceptionally(e -> {
final WebhookDispatcher webhook = jda.getConsoleWebhook();
if (webhook == null || webhook.isShutdown()) {
return;
}
webhook.send(jda.getWebhookMessage(content)).exceptionally(e -> {
logger.severe(tlLiteral("discordErrorWebhook"));
remove();
return null;
Expand All @@ -97,40 +77,13 @@ public void append(LogEvent event) {
return;
}

if (entry.startsWith("EssXBackoff: ")) {
return;
}

if (event.getLoggerName().contains("club.minnced.discord.webhook.WebhookClient") && entry.startsWith("Encountered 429, retrying after ")) {
if (recentRateLimit.get() >= 0) {
recentRateLimit.incrementAndGet();
}

if (lastRateLimitTime.get() == 0 || System.currentTimeMillis() - lastRateLimitTime.get() > 5000) {
lastRateLimitTime.set(System.currentTimeMillis());

// A negative value would mean the timer is current preforming a backoff, don't stop it.
if (recentRateLimit.get() >= 0) {
recentRateLimit.set(0);
}
} else if (recentRateLimit.get() >= 2) {
// Start the webhook backoff, defaulting to 20s, which should reset our bucket.
if (jda.isDebug()) {
totalBackoffEvents.getAndIncrement();
logger.warning("EssXBackoff: Beginning Webhook Backoff");
}
recentRateLimit.set(-20);
}
return;
}

final String[] loggerNameSplit = event.getLoggerName().split("\\.");
final String loggerName = loggerNameSplit[loggerNameSplit.length - 1].trim();

if (!loggerName.isEmpty()) {
entry = "[" + loggerName + "] " + entry;
}

if (!jda.getSettings().getConsoleFilters().isEmpty()) {
for (final Pattern pattern : jda.getSettings().getConsoleFilters()) {
if (pattern.matcher(entry).find()) {
Expand All @@ -139,11 +92,18 @@ public void append(LogEvent event) {
}
}

messageQueue.addAll(Splitter.fixedLength(Message.MAX_CONTENT_LENGTH - 50).splitToList(
for (final String line : Splitter.fixedLength(Message.MAX_CONTENT_LENGTH - 50).splitToList(
MessageUtil.formatMessage(jda.getSettings().getConsoleFormat(),
TimeFormat.TIME_LONG.format(Instant.now()),
event.getLevel().name(),
MessageUtil.sanitizeDiscordMarkdown(entry))));
MessageUtil.sanitizeDiscordMarkdown(entry)))) {

if (!messageQueue.offer(line)) {
if (jda.isDebug()) {
logger.fine("Console relay queue full, dropping message.");
}
}
}
}

public void remove() {
Expand Down
Loading
Loading