From 4c6ab2c1a0e71bac024c399bc49ff6a682f7f1a9 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 8 Apr 2025 20:52:54 +0200 Subject: [PATCH 1/5] Improve and fix the discord rate limit warnings --- .../velocitycore/discord/DiscordBot.java | 11 +++- .../discord/channels/DiscordChannel.java | 61 +++++++++++-------- .../discord/channels/DiscordChatRoom.java | 4 +- .../listeners/DiscordTicketHandler.java | 2 +- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java index ecdc5a53..8b522342 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java @@ -167,14 +167,19 @@ public class DiscordBot { checklistChannel = new ChecklistChannel(config.channel("checklist")); config.getCouncilThread().forEach((roleId, threadId) -> new CouncilChannel(DiscordBot.getGuild().getRoleById(roleId), DiscordBot.getGuild().getThreadChannelById(threadId))); - announcementChannel = new DiscordChannel(config.channel("announcement")) { + announcementChannel = new DiscordChannel(config.channel("announcement"), 0) { @Override public void received(MessageReceivedEvent event) { Chatter.broadcast().system("ALERT", event.getMessage().getContentDisplay()); } }; - ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast); - serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam); + + // There is a hard limit of 30 messages per minute to send as a webhook, thus with 5 webhooks we can send + // 180 messages per minute. Which means 3 every second. I looked at the WGS fights and there were around + // ~70 in a short burst and then rather long no new message. + ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast, 5); + // 30 messages per minute should be enough for the server team! + serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam, 1); VelocityCore.schedule(() -> { try { diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java index 7092aeb4..08b2b3cd 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java @@ -26,17 +26,21 @@ import de.steamwar.velocitycore.discord.DiscordBot; import de.steamwar.velocitycore.discord.listeners.ChannelListener; import lombok.AllArgsConstructor; import lombok.Getter; -import net.dv8tion.jda.api.entities.Icon; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.Webhook; +import net.dv8tion.jda.api.entities.WebhookClient; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.utils.ImageProxy; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.internal.requests.IncomingWebhookClientImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import java.util.ArrayDeque; +import java.util.Queue; + @AllArgsConstructor public class DiscordChannel extends Chatter.PlayerlessChatter { @@ -46,31 +50,37 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { return user != null ? user : SteamwarUser.get(0); } + private final Queue webhooks = new ArrayDeque<>(); + private final int maxNumberOfWebhooks; + private final SteamwarUser user; @Getter private final MessageChannel channel; public DiscordChannel(User user) { - this(userOrPublic(user), user.openPrivateChannel().complete()); + this(0, userOrPublic(user), user.openPrivateChannel().complete()); } - public DiscordChannel(String channel) { - this(DiscordBot.getGuild().getTextChannelById(channel)); + public DiscordChannel(String channel, int maxNumberOfWebhooks) { + this(DiscordBot.getGuild().getTextChannelById(channel), maxNumberOfWebhooks); } - public DiscordChannel(MessageChannel channel) { - this(SteamwarUser.get(-1), channel); + public DiscordChannel(MessageChannel channel, int maxNumberOfWebhooks) { + this(maxNumberOfWebhooks, SteamwarUser.get(-1), channel); ChannelListener.getChannels().put(this.channel, this); + if (channel instanceof TextChannel) { + webhooks.addAll(((TextChannel) channel).retrieveWebhooks().complete()); + } } - public void send(String message) { + public synchronized void send(String message) { message = message .replace("&", "") .replace("@everyone", "`@everyone`") .replace("@here", "`@here`") .replaceAll("<[@#]!?\\d+>", "`$0`"); - if (getChannel() instanceof TextChannel && message.contains("»")) { + if (maxNumberOfWebhooks > 0 && getChannel() instanceof TextChannel && message.contains("»")) { String[] strings = message.split("»", 2); String userName = strings[0]; String sendMessage = strings[1]; @@ -85,29 +95,28 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { return; } - ImageProxy avatarUrl; + String avatarUrl; if (user.getDiscordId() != null) { - avatarUrl = DiscordBot.getGuild().retrieveMemberById(user.getDiscordId()).complete().getEffectiveAvatar(); + avatarUrl = DiscordBot.getGuild().retrieveMemberById(user.getDiscordId()).complete().getEffectiveAvatarUrl(); } else { - avatarUrl = DiscordBot.getInstance().getJda().getSelfUser().getAvatar(); + avatarUrl = DiscordBot.getInstance().getJda().getSelfUser().getAvatarUrl(); } TextChannel textChannel = (TextChannel) getChannel(); - try { - textChannel.createWebhook(userName) - .setAvatar(Icon.from(avatarUrl.download(128).get())) - .onSuccess(webhook -> { - webhook.sendMessage(sendMessage) - .onSuccess(__ -> { - webhook.delete().queue(); - }) - .queue(); - }) - .queue(); - return; - } catch (Exception e) { - // Ignore and send message as normal! + if (webhooks.size() < maxNumberOfWebhooks) { + webhooks.add(textChannel.createWebhook(DiscordBot.getInstance().getJda().getSelfUser().getName() + "_" + webhooks.size()).complete()); } + Webhook webhook = webhooks.poll(); + webhooks.add(webhook); + + // This works as per this documentation: https://discord.com/developers/docs/resources/webhook#execute-webhook + IncomingWebhookClientImpl webhookClient = (IncomingWebhookClientImpl) WebhookClient.createClient(DiscordBot.getInstance().getJda(), webhook.getUrl()); + webhookClient.sendRequest() + .setUsername(userName) + .setAvatarUrl(avatarUrl) + .setContent(sendMessage) + .queue(); + return; } send(new MessageCreateBuilder() diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChatRoom.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChatRoom.java index 7b05357c..2db981f1 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChatRoom.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChatRoom.java @@ -33,8 +33,8 @@ public class DiscordChatRoom extends DiscordChannel { private final String format; private final Supplier target; - public DiscordChatRoom(String channel, String format, Supplier target) { - super(channel); + public DiscordChatRoom(String channel, String format, Supplier target, int maxNumberOfWebhooks) { + super(channel, maxNumberOfWebhooks); this.format = format; this.target = target; } diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java index f32b4767..d58fc6a5 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java @@ -70,7 +70,7 @@ public class DiscordTicketHandler extends ListenerAdapter { Permission.MESSAGE_HISTORY).complete(); ticketChannel.getManager().setTopic(event.getUser().getId()).complete(); - DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel); + DiscordChannel channel = new DiscordChannel(0, DiscordChannel.userOrPublic(event.getUser()), ticketChannel); channel.send(new MessageCreateBuilder() .setEmbeds(new EmbedBuilder() .setTitle(channel.parseToPlain("DC_TICKET_TITLE")) From 038f54c3b31d9dbab3e0d00d0340728d01297bd1 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Wed, 9 Apr 2025 09:03:07 +0200 Subject: [PATCH 2/5] Make it buildable and reduce complexity --- .../discord/channels/ChecklistChannel.java | 2 +- .../discord/channels/DiscordChannel.java | 14 +++++++++----- .../discord/channels/StaticMessageChannel.java | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/ChecklistChannel.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/ChecklistChannel.java index 6f19f34e..18bbff4a 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/ChecklistChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/ChecklistChannel.java @@ -32,7 +32,7 @@ public class ChecklistChannel extends DiscordChannel { private final List lastSchematics = new ArrayList<>(); public ChecklistChannel(String channel) { - super(channel); + super(channel, 0); } public void update() { diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java index 08b2b3cd..eab85cc1 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java @@ -68,8 +68,16 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { public DiscordChannel(MessageChannel channel, int maxNumberOfWebhooks) { this(maxNumberOfWebhooks, SteamwarUser.get(-1), channel); ChannelListener.getChannels().put(this.channel, this); + if (channel instanceof TextChannel) { - webhooks.addAll(((TextChannel) channel).retrieveWebhooks().complete()); + TextChannel textChannel = (TextChannel) channel; + webhooks.addAll(textChannel.retrieveWebhooks().complete()); + while (webhooks.size() > maxNumberOfWebhooks) { + webhooks.remove().delete().queue(); + } + while (webhooks.size() < maxNumberOfWebhooks) { + webhooks.add(textChannel.createWebhook(DiscordBot.getInstance().getJda().getSelfUser().getName()).complete()); + } } } @@ -102,10 +110,6 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { avatarUrl = DiscordBot.getInstance().getJda().getSelfUser().getAvatarUrl(); } - TextChannel textChannel = (TextChannel) getChannel(); - if (webhooks.size() < maxNumberOfWebhooks) { - webhooks.add(textChannel.createWebhook(DiscordBot.getInstance().getJda().getSelfUser().getName() + "_" + webhooks.size()).complete()); - } Webhook webhook = webhooks.poll(); webhooks.add(webhook); diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/StaticMessageChannel.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/StaticMessageChannel.java index 69549d38..9ab2920e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/StaticMessageChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/StaticMessageChannel.java @@ -46,14 +46,14 @@ public class StaticMessageChannel extends DiscordChannel { } public StaticMessageChannel(String channel, Supplier supplier, Consumer interaction) { - super(channel); + super(channel, 0); this.supplier = supplier; this.interaction = interaction; init(); } public StaticMessageChannel(MessageChannel channel, Supplier supplier, Consumer interaction) { - super(channel); + super(channel, 0); this.supplier = supplier; this.interaction = interaction; init(); From 4a816696ec27e60cf2b803158c3c5af3db9f0c8e Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Wed, 9 Apr 2025 09:10:30 +0200 Subject: [PATCH 3/5] Update the message limits --- .../src/de/steamwar/velocitycore/discord/DiscordBot.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java index 8b522342..6ea27e2f 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java @@ -175,11 +175,11 @@ public class DiscordBot { }; // There is a hard limit of 30 messages per minute to send as a webhook, thus with 5 webhooks we can send - // 180 messages per minute. Which means 3 every second. I looked at the WGS fights and there were around + // 240 messages per minute. Which means 4 every second. I looked at the WGS fights and there were around // ~70 in a short burst and then rather long no new message. - ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast, 5); - // 30 messages per minute should be enough for the server team! - serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam, 1); + ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast, 8); + // 60 messages per minute should be enough for the server team! + serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam, 2); VelocityCore.schedule(() -> { try { From e1bcdd59baf94934d283e17cfab50fbe7eaa50a5 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Mon, 14 Apr 2025 09:14:04 +0200 Subject: [PATCH 4/5] Move 'maxNumberOfWebhooks' constructor parameter to end of list --- .../velocitycore/discord/channels/DiscordChannel.java | 9 +++++---- .../discord/listeners/DiscordTicketHandler.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java index eab85cc1..5fcbcf6a 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/channels/DiscordChannel.java @@ -51,14 +51,15 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { } private final Queue webhooks = new ArrayDeque<>(); - private final int maxNumberOfWebhooks; private final SteamwarUser user; @Getter private final MessageChannel channel; + private final int maxNumberOfWebhooks; + public DiscordChannel(User user) { - this(0, userOrPublic(user), user.openPrivateChannel().complete()); + this(userOrPublic(user), user.openPrivateChannel().complete(), 0); } public DiscordChannel(String channel, int maxNumberOfWebhooks) { @@ -66,7 +67,7 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { } public DiscordChannel(MessageChannel channel, int maxNumberOfWebhooks) { - this(maxNumberOfWebhooks, SteamwarUser.get(-1), channel); + this(SteamwarUser.get(-1), channel, maxNumberOfWebhooks); ChannelListener.getChannels().put(this.channel, this); if (channel instanceof TextChannel) { @@ -81,7 +82,7 @@ public class DiscordChannel extends Chatter.PlayerlessChatter { } } - public synchronized void send(String message) { + public void send(String message) { message = message .replace("&", "") .replace("@everyone", "`@everyone`") diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java index d58fc6a5..5c5e708b 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/listeners/DiscordTicketHandler.java @@ -70,7 +70,7 @@ public class DiscordTicketHandler extends ListenerAdapter { Permission.MESSAGE_HISTORY).complete(); ticketChannel.getManager().setTopic(event.getUser().getId()).complete(); - DiscordChannel channel = new DiscordChannel(0, DiscordChannel.userOrPublic(event.getUser()), ticketChannel); + DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel, 0); channel.send(new MessageCreateBuilder() .setEmbeds(new EmbedBuilder() .setTitle(channel.parseToPlain("DC_TICKET_TITLE")) From 5b3c3f36b47c07f414fcb376df5ef5d71754b7ca Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Mon, 14 Apr 2025 09:16:06 +0200 Subject: [PATCH 5/5] Fix comment --- .../src/de/steamwar/velocitycore/discord/DiscordBot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java index 6ea27e2f..637b0c7e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java @@ -174,7 +174,7 @@ public class DiscordBot { } }; - // There is a hard limit of 30 messages per minute to send as a webhook, thus with 5 webhooks we can send + // There is a hard limit of 30 messages per minute to send as a webhook, thus with 8 webhooks we can send // 240 messages per minute. Which means 4 every second. I looked at the WGS fights and there were around // ~70 in a short burst and then rather long no new message. ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast, 8);