diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 6fa2873afa2..2ada7553218 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -328,6 +328,7 @@ private void runDump(Server server, CommandSource sender, String commandLabel, S files.add(new PasteUtil.PasteFile("dump.json", dump.toString())); final Plugin essDiscord = Bukkit.getPluginManager().getPlugin("EssentialsDiscord"); + final Plugin essDiscordLink = Bukkit.getPluginManager().getPlugin("EssentialsDiscordLink"); final Plugin essSpawn = Bukkit.getPluginManager().getPlugin("EssentialsSpawn"); final Map knownCommandsCopy = new HashMap<>(ess.getKnownCommandsProvider().getKnownCommands()); @@ -389,6 +390,15 @@ private void runDump(Server server, CommandSource sender, String commandLabel, S } catch (IOException e) { sender.sendMessage(tl("dumpErrorUpload", "discord-config.yml", e.getMessage())); } + + if (essDiscordLink != null) { + try { + files.add(new PasteUtil.PasteFile("discord-link-config.yml", + new String(Files.readAllBytes(essDiscordLink.getDataFolder().toPath().resolve("config.yml")), StandardCharsets.UTF_8))); + } catch (IOException e) { + sender.sendMessage(tl("dumpErrorUpload", "discord-link-config.yml", e.getMessage())); + } + } } if (kits) { diff --git a/EssentialsDiscord/build.gradle b/EssentialsDiscord/build.gradle index 20e675be788..555fec855cf 100644 --- a/EssentialsDiscord/build.gradle +++ b/EssentialsDiscord/build.gradle @@ -4,12 +4,14 @@ plugins { dependencies { compileOnly project(':EssentialsX') - implementation('net.dv8tion:JDA:4.4.1_353') { + implementation('net.dv8tion:JDA:5.0.0-beta.12') { exclude(module: 'opus-java') } - implementation 'com.vdurmont:emoji-java:5.1.1' - implementation 'club.minnced:discord-webhooks:0.7.2' - compileOnly 'org.apache.logging.log4j:log4j-core:2.15.0' + implementation 'com.github.MinnDevelopment:emoji-java:v6.1.0' + implementation('club.minnced:discord-webhooks:0.8.2') { + exclude(module: 'okhttp') + } + compileOnly 'org.apache.logging.log4j:log4j-core:2.17.1' compileOnly 'me.clip:placeholderapi:2.10.9' } @@ -20,15 +22,17 @@ shadowJar { include(dependency('com.neovisionaries:nv-websocket-client')) include(dependency('com.squareup.okhttp3:okhttp')) include(dependency('com.squareup.okio:okio')) + include(dependency('com.squareup.okio:okio-jvm')) include(dependency('org.apache.commons:commons-collections4')) include(dependency('net.sf.trove4j:trove4j')) include(dependency('com.fasterxml.jackson.core:jackson-databind')) include(dependency('com.fasterxml.jackson.core:jackson-core')) include(dependency('com.fasterxml.jackson.core:jackson-annotations')) include(dependency('org.slf4j:slf4j-api')) + include(dependency('org.jetbrains.kotlin:kotlin-stdlib')) // Emoji - include(dependency('com.vdurmont:emoji-java')) + include(dependency('com.github.MinnDevelopment:emoji-java')) include(dependency('org.json:json')) // discord-webhooks @@ -47,6 +51,7 @@ shadowJar { relocate 'com.fasterxml.jackson.core', 'net.essentialsx.dep.com.fasterxml.jackson.core' relocate 'com.fasterxml.jackson.annotation', 'net.essentialsx.dep.com.fasterxml.jackson.annotation' relocate 'gnu.trove', 'net.essentialsx.dep.gnu.trove' + relocate 'kotlin', 'net.essentialsx.dep.garbage.kotlin' // Emoji relocate 'com.vdurmont.emoji', 'net.essentialsx.dep.com.vdurmont.emoji' diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java index d7030219939..beb62b33be1 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java @@ -168,6 +168,10 @@ public List getConsoleFilters() { return consoleFilter; } + public int getConsoleSkipDelay() { + return config.getInt("console.skip-delay", 2); + } + public boolean isShowAvatar() { return config.getBoolean("show-avatar", false); } @@ -472,7 +476,7 @@ public void reloadConfig() { activityType = Activity.ActivityType.valueOf(activity); } } catch (IllegalArgumentException e) { - activityType = Activity.ActivityType.DEFAULT; + activityType = Activity.ActivityType.PLAYING; } if (activityType != null) { statusActivity = Activity.of(activityType, config.getString("presence.message", "Minecraft")); diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java index 71e35460e2d..4883cb634b4 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java @@ -1,6 +1,5 @@ package net.essentialsx.discord; -import club.minnced.discord.webhook.WebhookClient; import club.minnced.discord.webhook.WebhookClientBuilder; import club.minnced.discord.webhook.send.WebhookMessage; import club.minnced.discord.webhook.send.WebhookMessageBuilder; @@ -11,14 +10,14 @@ import com.earth2me.essentials.utils.VersionUtil; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; -import net.dv8tion.jda.api.entities.Emote; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.Webhook; -import net.dv8tion.jda.api.events.ShutdownEvent; -import net.dv8tion.jda.api.hooks.EventListener; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; +import net.dv8tion.jda.api.events.session.ShutdownEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.utils.cache.CacheFlag; import net.ess3.nms.refl.providers.AchievementListenerProvider; import net.ess3.nms.refl.providers.AdvancementListenerProvider; @@ -46,6 +45,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 org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; @@ -79,11 +79,11 @@ public class JDADiscordService implements DiscordService, IEssentialsModule { private JDA jda; private Guild guild; private TextChannel primaryChannel; - private WebhookClient consoleWebhook; + private WrappedWebhookClient consoleWebhook; private String lastConsoleId; private final Map registeredTypes = new HashMap<>(); private final Map typeToChannelId = new HashMap<>(); - private final Map channelIdToWebhook = new HashMap<>(); + private final Map channelIdToWebhook = new HashMap<>(); private ConsoleInjector injector; private DiscordCommandDispatcher commandDispatcher; private InteractionControllerImpl interactionController; @@ -140,7 +140,7 @@ public void sendMessage(DiscordMessageEvent event, String message, boolean group final String webhookChannelId = typeToChannelId.get(event.getType()); if (webhookChannelId != null) { - final WebhookClient client = channelIdToWebhook.get(webhookChannelId); + final WrappedWebhookClient client = channelIdToWebhook.get(webhookChannelId); if (client != null) { final String avatarUrl = event.getAvatarUrl() != null ? event.getAvatarUrl() : jda.getSelfUser().getAvatarUrl(); final String name = event.getName() != null ? event.getName() : guild.getSelfMember().getEffectiveName(); @@ -154,7 +154,7 @@ public void sendMessage(DiscordMessageEvent event, String message, boolean group return; } channel.sendMessage(strippedContent) - .allowedMentions(groupMentions ? null : DiscordUtil.NO_GROUP_MENTIONS) + .setAllowedMentions(groupMentions ? null : DiscordUtil.NO_GROUP_MENTIONS) .queue(); } @@ -169,7 +169,8 @@ public void startup() throws LoginException, InterruptedException { jda = JDABuilder.createDefault(plugin.getSettings().getBotToken()) .addEventListeners(new DiscordListener(this)) - .enableCache(CacheFlag.EMOTE) + .enableIntents(GatewayIntent.MESSAGE_CONTENT) + .enableCache(CacheFlag.EMOJI) .disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE) .setContextEnabled(false) .build() @@ -205,7 +206,7 @@ public void startup() throws LoginException, InterruptedException { } // Load emotes into cache, JDA will handle updates from here on out. - guild.retrieveEmotes().queue(); + guild.retrieveEmojis().queue(); updatePrimaryChannel(); @@ -323,10 +324,10 @@ public InteractionController getInteractionController() { public void updatePrimaryChannel() { TextChannel channel = guild.getTextChannelById(plugin.getSettings().getPrimaryChannelId()); if (channel == null) { - channel = guild.getDefaultChannel(); - if (channel == null) { + if (!(guild.getDefaultChannel() instanceof TextChannel)) { throw new RuntimeException(tl("discordErrorNoPerms")); } + channel = (TextChannel) guild.getDefaultChannel(); logger.warning(tl("discordErrorNoPrimary", channel.getName())); } @@ -337,7 +338,7 @@ public void updatePrimaryChannel() { } public String parseMessageEmotes(String message) { - for (final Emote emote : guild.getEmoteCache()) { + for (final RichCustomEmoji emote : guild.getEmojiCache()) { message = message.replaceAll(":" + Pattern.quote(emote.getName()) + ":", emote.getAsMention()); } return message; @@ -362,7 +363,7 @@ public void updatePresence() { public void updateTypesRelay() { if (!getSettings().isShowAvatar() && !getSettings().isShowName() && !getSettings().isShowDisplayName()) { - for (WebhookClient webhook : channelIdToWebhook.values()) { + for (WrappedWebhookClient webhook : channelIdToWebhook.values()) { webhook.close(); } typeToChannelId.clear(); @@ -382,7 +383,7 @@ public void updateTypesRelay() { final Webhook webhook = DiscordUtil.getOrCreateWebhook(channel, DiscordUtil.ADVANCED_RELAY_NAME).join(); if (webhook == null) { - final WebhookClient current = channelIdToWebhook.get(channel.getId()); + final WrappedWebhookClient current = channelIdToWebhook.get(channel.getId()); if (current != null) { current.close(); } @@ -483,15 +484,13 @@ public void shutdown() { shutdownConsoleRelay(true); - for (WebhookClient webhook : channelIdToWebhook.values()) { + for (WrappedWebhookClient webhook : channelIdToWebhook.values()) { webhook.close(); } // Unregister leftover jda listeners for (Object obj : jda.getRegisteredListeners()) { - if (!(obj instanceof EventListener)) { // Yeah bro I wish I knew too :/ - jda.removeEventListener(obj); - } + jda.removeEventListener(obj); } // Unregister Bukkit Events @@ -512,7 +511,7 @@ public void onShutdown(@NotNull ShutdownEvent event) { // Wait for JDA to wrap it up future.get(5, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.warning("JDA took longer than expected to shutdown, this may have caused some problems."); + logger.log(Level.WARNING, "JDA took longer than expected to shutdown, this may have caused some problems.", e); } finally { jda = null; } @@ -588,7 +587,7 @@ public DiscordSettings getSettings() { return plugin.getSettings(); } - public WebhookClient getConsoleWebhook() { + public WrappedWebhookClient getConsoleWebhook() { return consoleWebhook; } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/commands/Commanddiscordbroadcast.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/commands/Commanddiscordbroadcast.java index d94fa9274f5..b5583b8fce9 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/commands/Commanddiscordbroadcast.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/commands/Commanddiscordbroadcast.java @@ -4,8 +4,8 @@ import com.earth2me.essentials.commands.EssentialsCommand; import com.earth2me.essentials.commands.NotEnoughArgumentsException; import com.vdurmont.emoji.EmojiParser; -import net.dv8tion.jda.api.entities.Emote; -import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; import net.essentialsx.discord.JDADiscordService; import net.essentialsx.discord.util.DiscordUtil; import net.essentialsx.discord.util.MessageUtil; @@ -48,7 +48,7 @@ protected void run(Server server, CommandSource sender, String commandLabel, Str } channel.sendMessage(jda.parseMessageEmotes(message)) - .allowedMentions(sender.isAuthorized("essentials.discordbroadcast.ping", ess) ? null : DiscordUtil.NO_GROUP_MENTIONS) + .setAllowedMentions(sender.isAuthorized("essentials.discordbroadcast.ping", ess) ? null : DiscordUtil.NO_GROUP_MENTIONS) .queue(); sender.sendMessage(tl("discordbroadcastSent", "#" + EmojiParser.parseToAliases(channel.getName()))); @@ -65,12 +65,12 @@ protected List getTabCompleteOptions(Server server, CommandSource sender final String curArg = args[args.length - 1]; if (!curArg.isEmpty() && curArg.charAt(0) == ':' && (curArg.length() == 1 || curArg.charAt(curArg.length() - 1) != ':')) { final JDADiscordService jda = (JDADiscordService) module; - if (jda.getGuild().getEmoteCache().isEmpty()) { + if (jda.getGuild().getEmojiCache().isEmpty()) { return Collections.emptyList(); } final List completions = new ArrayList<>(); - for (final Emote emote : jda.getGuild().getEmoteCache()) { + for (final RichCustomEmoji emote : jda.getGuild().getEmojiCache()) { completions.add(":" + emote.getName() + ":"); } return completions; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionChannelImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionChannelImpl.java index 3963bd60f89..4112311c984 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionChannelImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionChannelImpl.java @@ -1,12 +1,13 @@ package net.essentialsx.discord.interactions; -import net.dv8tion.jda.api.entities.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.essentialsx.api.v2.services.discord.InteractionChannel; public class InteractionChannelImpl implements InteractionChannel { - private final GuildChannel channel; + private final GuildMessageChannel channel; - public InteractionChannelImpl(GuildChannel channel) { + public InteractionChannelImpl(GuildMessageChannel channel) { this.channel = channel; } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java index 68f2423c6a6..d4cd81cfdf9 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java @@ -1,12 +1,14 @@ package net.essentialsx.discord.interactions; import com.earth2me.essentials.utils.StringUtil; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.requests.ErrorResponse; import net.essentialsx.api.v2.services.discord.InteractionCommand; import net.essentialsx.api.v2.services.discord.InteractionCommandArgument; @@ -42,7 +44,7 @@ public InteractionControllerImpl(JDADiscordService jda) { } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { + public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { if (event.getGuild() == null || event.getMember() == null || !commandMap.containsKey(event.getName())) { return; } @@ -77,7 +79,7 @@ public void processBatchRegistration() { for (final InteractionCommand command : batchRegistrationQueue.values()) { // German is quite the language final String description = StringUtil.abbreviate(command.getDescription(), 100); - final CommandData data = new CommandData(command.getName(), description); + final SlashCommandData data = Commands.slash(command.getName(), description); if (command.getArguments() != null) { for (final InteractionCommandArgument argument : command.getArguments()) { // German doesn't support spaces between words @@ -132,7 +134,7 @@ public void registerCommand(InteractionCommand command) throws InteractionExcept return; } - final CommandData data = new CommandData(command.getName(), command.getDescription()); + final SlashCommandData data = Commands.slash(command.getName(), command.getDescription()); if (command.getArguments() != null) { for (final InteractionCommandArgument argument : command.getArguments()) { data.addOption(OptionType.valueOf(argument.getType().name()), argument.getName(), argument.getDescription(), argument.isRequired()); diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java index b43030d04fc..01891641571 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionEventImpl.java @@ -2,10 +2,10 @@ import com.earth2me.essentials.utils.FormatUtil; import com.google.common.base.Joiner; -import net.dv8tion.jda.api.MessageBuilder; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.utils.messages.MessageEditBuilder; import net.essentialsx.api.v2.services.discord.InteractionChannel; import net.essentialsx.api.v2.services.discord.InteractionEvent; import net.essentialsx.api.v2.services.discord.InteractionMember; @@ -23,11 +23,11 @@ */ public class InteractionEventImpl implements InteractionEvent { private final static Logger logger = EssentialsDiscord.getWrappedLogger(); - private final SlashCommandEvent event; + private final SlashCommandInteractionEvent event; private final InteractionMember member; private final List replyBuffer = new ArrayList<>(); - public InteractionEventImpl(final SlashCommandEvent jdaEvent) { + public InteractionEventImpl(final SlashCommandInteractionEvent jdaEvent) { this.event = jdaEvent; this.member = new InteractionMemberImpl(jdaEvent.getMember()); } @@ -39,7 +39,7 @@ public void reply(String message) { String reply = Joiner.on('\n').join(replyBuffer); reply = reply.substring(0, Math.min(Message.MAX_CONTENT_LENGTH, reply.length())); event.getHook().editOriginal( - new MessageBuilder() + new MessageEditBuilder() .setContent(reply) .setAllowedMentions(DiscordUtil.NO_GROUP_MENTIONS).build()) .queue(null, error -> logger.log(Level.SEVERE, "Error while editing command interaction response", error)); @@ -77,7 +77,7 @@ public InteractionMember getUserArgument(String key) { @Override public InteractionChannel getChannelArgument(String key) { final OptionMapping mapping = event.getOption(key); - return mapping == null ? null : new InteractionChannelImpl(mapping.getAsGuildChannel()); + return mapping == null ? null : new InteractionChannelImpl(mapping.getAsChannel().asGuildMessageChannel()); } @Override diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionMemberImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionMemberImpl.java index 9abfb994c79..a6111418ec8 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionMemberImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionMemberImpl.java @@ -2,8 +2,8 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.PrivateChannel; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel; import net.essentialsx.api.v2.services.discord.InteractionMember; import net.essentialsx.api.v2.services.discord.InteractionRole; import net.essentialsx.discord.util.DiscordUtil; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java index 20fa1ff0a96..c735aa7bdc9 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java @@ -1,6 +1,7 @@ package net.essentialsx.discord.listeners; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.essentialsx.discord.JDADiscordService; import net.essentialsx.discord.util.DiscordCommandSender; @@ -18,7 +19,11 @@ public DiscordCommandDispatcher(JDADiscordService jda) { } @Override - public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { + public void onMessageReceived(@NotNull MessageReceivedEvent event) { + if (event.getMessage().getChannelType() != ChannelType.TEXT) { + return; + } + if (jda.getConsoleWebhook() != null && event.getChannel().getId().equals(channelId)) { if ((event.isWebhookMessage() || event.getAuthor().isBot()) && (!jda.getSettings().isConsoleBotCommandRelay() || DiscordUtil.ACTIVE_WEBHOOKS.contains(event.getAuthor().getId()) || event.getAuthor().getId().equals(event.getGuild().getSelfMember().getId()))) { return; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java index bdc8aa6de1c..c8725c514da 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordListener.java @@ -6,7 +6,8 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.ess3.api.IUser; import net.essentialsx.api.v2.events.discord.DiscordRelayEvent; @@ -36,7 +37,11 @@ public DiscordListener(JDADiscordService plugin) { } @Override - public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { + public void onMessageReceived(@NotNull MessageReceivedEvent event) { + if (event.getMessage().getChannelType() != ChannelType.TEXT) { + return; + } + if (event.getAuthor().isBot() && !event.isWebhookMessage() && (!plugin.getSettings().isShowBotMessages() || event.getAuthor().getId().equals(plugin.getJda().getSelfUser().getId()))) { return; } @@ -120,7 +125,7 @@ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent event) { // Do not create the event specific objects if there are no listeners if (DiscordRelayEvent.getHandlerList().getRegisteredListeners().length != 0) { final DiscordRelayEvent relayEvent = new DiscordRelayEvent( - new InteractionMemberImpl(member), new InteractionChannelImpl(event.getChannel()), + new InteractionMemberImpl(member), new InteractionChannelImpl(event.getGuildChannel()), Collections.unmodifiableList(keys), event.getMessage().getContentRaw(), formattedMessage, viewers); Bukkit.getPluginManager().callEvent(relayEvent); if (relayEvent.isCancelled()) { diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java index 3d2502770aa..01f5d365d01 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java @@ -16,6 +16,8 @@ import java.time.Instant; 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.tl; @@ -24,16 +26,41 @@ 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 JDADiscordService jda; private final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); 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) { @@ -47,7 +74,7 @@ public ConsoleInjector(JDADiscordService jda) { if (buffer.length() != 0) { sendMessage(buffer.toString()); } - }, 20, 40).getTaskId(); + }, 20, 20 * QUEUE_PROCESS_PERIOD_SECONDS).getTaskId(); } private void sendMessage(String content) { @@ -70,6 +97,33 @@ 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; + } + if (!jda.getSettings().getConsoleFilters().isEmpty()) { for (final Pattern pattern : jda.getSettings().getConsoleFilters()) { if (pattern.matcher(entry).find()) { @@ -78,12 +132,14 @@ public void append(LogEvent event) { } } - final String loggerName = event.getLoggerName(); - if (!loggerName.isEmpty() && !loggerName.contains(".")) { - entry = "[" + event.getLoggerName() + "] " + entry; + final String[] loggerNameSplit = event.getLoggerName().split("\\."); + final String loggerName = loggerNameSplit[loggerNameSplit.length - 1].trim(); + + if (!loggerName.isEmpty()) { + entry = "[" + loggerName + "] " + entry; } - messageQueue.addAll(Splitter.fixedLength(Message.MAX_CONTENT_LENGTH - 2).splitToList( + messageQueue.addAll(Splitter.fixedLength(Message.MAX_CONTENT_LENGTH - 50).splitToList( MessageUtil.formatMessage(jda.getSettings().getConsoleFormat(), TimeFormat.TIME_LONG.format(Instant.now()), event.getLevel().name(), @@ -94,6 +150,9 @@ public void remove() { ((Logger) LogManager.getRootLogger()).removeAppender(this); Bukkit.getScheduler().cancelTask(taskId); messageQueue.clear(); + if (jda.getConsoleWebhook() != null && !jda.getConsoleWebhook().isShutdown()) { + jda.getConsoleWebhook().close(); + } removed = true; } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java index dfe95366279..d789d6d74a4 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java @@ -1,7 +1,6 @@ package net.essentialsx.discord.util; import club.minnced.discord.webhook.WebhookClient; -import club.minnced.discord.webhook.WebhookClientBuilder; import club.minnced.discord.webhook.send.AllowedMentions; import com.earth2me.essentials.utils.DownsampleUtil; import com.earth2me.essentials.utils.FormatUtil; @@ -13,8 +12,8 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.Webhook; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.essentialsx.api.v2.events.discord.DiscordMessageEvent; import net.essentialsx.api.v2.services.discord.MessageType; import net.essentialsx.discord.JDADiscordService; @@ -39,7 +38,7 @@ public final class DiscordUtil { final ImmutableList.Builder types = new ImmutableList.Builder<>(); types.add(Message.MentionType.USER); types.add(Message.MentionType.CHANNEL); - types.add(Message.MentionType.EMOTE); + types.add(Message.MentionType.EMOJI); NO_GROUP_MENTIONS = types.build(); } @@ -54,13 +53,8 @@ private DiscordUtil() { * @param client The http client of the webhook. * @return The {@link WebhookClient}. */ - public static WebhookClient getWebhookClient(long id, String token, OkHttpClient client) { - return new WebhookClientBuilder(id, token) - .setWait(false) - .setAllowedMentions(AllowedMentions.none()) - .setHttpClient(client) - .setDaemon(true) - .build(); + public static WrappedWebhookClient getWebhookClient(long id, String token, OkHttpClient client) { + return new WrappedWebhookClient(id, token, client); } /** diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/WrappedWebhookClient.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/WrappedWebhookClient.java new file mode 100644 index 00000000000..7a8a4db46fc --- /dev/null +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/WrappedWebhookClient.java @@ -0,0 +1,88 @@ +package net.essentialsx.discord.util; + +import club.minnced.discord.webhook.WebhookClient; +import club.minnced.discord.webhook.WebhookClientBuilder; +import club.minnced.discord.webhook.receive.ReadonlyMessage; +import club.minnced.discord.webhook.send.AllowedMentions; +import club.minnced.discord.webhook.send.WebhookMessage; +import club.minnced.discord.webhook.util.ThreadPools; +import net.essentialsx.discord.EssentialsDiscord; +import okhttp3.OkHttpClient; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WrappedWebhookClient { + private final static Logger logger = EssentialsDiscord.getWrappedLogger(); + private final static MethodHandle queueGetter; + + static { + MethodHandle queueGetter1; + try { + final Field queueField = WebhookClient.class.getDeclaredField("queue"); + queueField.setAccessible(true); + queueGetter1 = MethodHandles.lookup().unreflectGetter(queueField); + } catch (NoSuchFieldException | IllegalAccessException e) { + logger.log(Level.SEVERE, "Could not acquire queue handle", e); + queueGetter1 = null; + } + queueGetter = queueGetter1; + } + + private final WebhookClient webhookClient; + private final ScheduledExecutorService executorService; + private final BlockingQueue webhookQueue; + + public WrappedWebhookClient(final long id, final String token, final OkHttpClient client) { + webhookClient = new WebhookClientBuilder(id, token) + .setWait(false) + .setAllowedMentions(AllowedMentions.none()) + .setHttpClient(client) + .setExecutorService(executorService = ThreadPools.getDefaultPool(id, null, true)) + .build(); + + BlockingQueue queue = null; + try { + queue = queueGetter == null ? null : (BlockingQueue) queueGetter.invoke(webhookClient); + } catch (Throwable e) { + e.printStackTrace(); + } + webhookQueue = queue; + } + + public CompletableFuture send(WebhookMessage message) { + return webhookClient.send(message); + } + + public boolean isShutdown() { + return webhookClient.isShutdown(); + } + + public void abandonRequests() { + if (webhookQueue != null) { + webhookQueue.clear(); + } + } + + public void close() { + // This call should close the executor service as well + webhookClient.close(); + if (executorService.isTerminated()) { + return; + } + try { + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + throw new InterruptedException("ExecutorService did not terminate in time."); + } + } catch (InterruptedException e) { + logger.log(Level.WARNING, "Webhook (ID: " + webhookClient.getId() + ") took longer than expected to shutdown, this may have caused some problems.", e); + } + } +} diff --git a/EssentialsDiscord/src/main/resources/config.yml b/EssentialsDiscord/src/main/resources/config.yml index 553acc7c8f7..9550cfd865f 100644 --- a/EssentialsDiscord/src/main/resources/config.yml +++ b/EssentialsDiscord/src/main/resources/config.yml @@ -111,6 +111,8 @@ console: # This is useful if you want to ignore certain messages from the console. # Can also be a list of regex patterns in addition to a single regex pattern. console-filter: "" + # The amount of minutes of delay before console messages sent to Discord should be skipped. + skip-delay: 2 # Configure which Discord channels different messages will be sent to. # You can either use the names of the channels listed above or just the id of a channel. diff --git a/settings.gradle.kts b/settings.gradle.kts index f133a0cd150..270e567afa0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,13 +4,11 @@ dependencyResolutionManagement { maven("https://papermc.io/repo/repository/maven-public/") maven("https://jitpack.io") { content { includeGroup("com.github.milkbowl") } + content { includeGroup("com.github.MinnDevelopment") } } maven("https://repo.codemc.org/repository/maven-public") { content { includeGroup("org.bstats") } } - maven("https://m2.dv8tion.net/releases/") { - content { includeGroup("net.dv8tion") } - } maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") { content { includeGroup("me.clip") } } @@ -18,6 +16,7 @@ dependencyResolutionManagement { content { includeGroup("com.mojang") } } mavenCentral { + content { includeGroup("net.dv8tion") } content { includeGroup("net.kyori") } content { includeGroup("org.apache.logging.log4j") } }