From a8cce7cf8d97760652a65d9e584bf41b5dcdd21f Mon Sep 17 00:00:00 2001 From: Vankka Date: Thu, 26 Aug 2021 20:07:16 +0300 Subject: [PATCH] Improve docs, change game chat events a bit, add in large errors for certain things going wrong with the Discord connection --- .../entity/channel/DiscordMessageChannel.java | 10 ++ .../message/ReceivedDiscordMessage.java | 13 +- .../ReceivedDiscordMessageCluster.java | 30 ++++ .../discord/api/entity/user/DiscordUser.java | 7 + .../discordsrv/api/event/bus/EventBus.java | 2 +- .../api/event/bus/EventListener.java | 2 +- .../event/bus/internal/EventStateHolder.java | 6 +- .../api/event/events/Cancellable.java | 2 +- .../api/event/events/Processable.java | 2 +- ...java => AbstractGameMessageSentEvent.java} | 55 ++----- ...ndEvent.java => ChatMessageSentEvent.java} | 9 +- .../discordsrv/common/AbstractDiscordSRV.java | 4 +- .../common/channel/ChannelConfig.java | 5 +- .../discordsrv/common/console/Console.java | 4 + .../ReceivedDiscordMessageClusterImpl.java | 55 +++++++ .../message/ReceivedDiscordMessageImpl.java | 14 +- .../connection/jda/JDAConnectionManager.java | 141 +++++++++++++++--- .../common/event/bus/EventBusImpl.java | 3 +- ...ener.java => DefaultGameChatListener.java} | 54 ++++--- 19 files changed, 313 insertions(+), 105 deletions(-) create mode 100644 api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessageCluster.java rename api/src/main/java/com/discordsrv/api/event/events/message/send/game/{AbstractGameMessageSendEvent.java => AbstractGameMessageSentEvent.java} (50%) rename api/src/main/java/com/discordsrv/api/event/events/message/send/game/{ChatMessageSendEvent.java => ChatMessageSentEvent.java} (79%) create mode 100644 common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageClusterImpl.java rename common/src/main/java/com/discordsrv/common/listener/{DefaultChatListener.java => DefaultGameChatListener.java} (66%) diff --git a/api/src/main/java/com/discordsrv/api/discord/api/entity/channel/DiscordMessageChannel.java b/api/src/main/java/com/discordsrv/api/discord/api/entity/channel/DiscordMessageChannel.java index 97663ab7..ae2ae80b 100644 --- a/api/src/main/java/com/discordsrv/api/discord/api/entity/channel/DiscordMessageChannel.java +++ b/api/src/main/java/com/discordsrv/api/discord/api/entity/channel/DiscordMessageChannel.java @@ -37,6 +37,7 @@ public interface DiscordMessageChannel extends Snowflake { /** * Sends the provided message to the channel. + * * @param message the channel to send to the channel * @return a future returning the message after being sent * @throws com.discordsrv.api.discord.api.exception.NotReadyException if DiscordSRV is not ready, {@link com.discordsrv.api.DiscordSRVApi#isReady()} @@ -44,8 +45,17 @@ public interface DiscordMessageChannel extends Snowflake { @NotNull CompletableFuture sendMessage(SendableDiscordMessage message); + /** + * Deletes the message identified by the id. + * + * @param id the id of the message to delete + * @return a future that will fail if the request fails + */ + CompletableFuture deleteMessageById(String id); + /** * Edits the message identified by the id. + * * @param id the id of the message to edit * @param message the new message content * @return a future returning the message after being edited diff --git a/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessage.java b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessage.java index 8b41cb00..08d30d32 100644 --- a/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessage.java +++ b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessage.java @@ -52,10 +52,19 @@ public interface ReceivedDiscordMessage extends SendableDiscordMessage, Snowflak } /** - * Edits this message to the provided message, the webhook username and avatar url will be ignored. + * Deletes this message. + * + * @return a future that will fail if the request fails + */ + CompletableFuture delete(); + + /** + * Edits this message to the provided message. * * @param message the new message - * @return the future for the message edit + * @return a future that will fail if the request fails, otherwise the new message provided by the request response + * @throws IllegalArgumentException if the message is not a webhook message, + * but the provided {@link SendableDiscordMessage} specifies a webhook username. */ @NotNull CompletableFuture edit(SendableDiscordMessage message); diff --git a/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessageCluster.java b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessageCluster.java new file mode 100644 index 00000000..f8557f75 --- /dev/null +++ b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/ReceivedDiscordMessageCluster.java @@ -0,0 +1,30 @@ +package com.discordsrv.api.discord.api.entity.message; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * A cluster of Discord messages, or just a single message. + */ +public interface ReceivedDiscordMessageCluster { + + /** + * Gets the messages in this cluster. + * @return the messages in this cluster + */ + List getMessages(); + + /** + * Deletes all the messages from this cluster, one request per message. + * @return a future that fails if any of the requests fail. + */ + CompletableFuture deleteAll(); + + /** + * Edits all the messages in this cluster, one request per edit. + * @param newMessage the new content of the messages + * @return a future that fails if any of the requests fail. + */ + CompletableFuture editAll(SendableDiscordMessage newMessage); + +} diff --git a/api/src/main/java/com/discordsrv/api/discord/api/entity/user/DiscordUser.java b/api/src/main/java/com/discordsrv/api/discord/api/entity/user/DiscordUser.java index 6963039d..a4c1967d 100644 --- a/api/src/main/java/com/discordsrv/api/discord/api/entity/user/DiscordUser.java +++ b/api/src/main/java/com/discordsrv/api/discord/api/entity/user/DiscordUser.java @@ -45,4 +45,11 @@ public interface DiscordUser extends Snowflake { @NotNull String getDiscriminator(); + /** + * Gets the Discord user's username followed by a {@code #} and their discriminator. + * @return the Discord user's username & discriminator in the following format {@code Username#1234} + */ + default String getAsTag() { + return getUsername() + "#" + getDiscriminator(); + } } diff --git a/api/src/main/java/com/discordsrv/api/event/bus/EventBus.java b/api/src/main/java/com/discordsrv/api/event/bus/EventBus.java index 3ddc09af..2ef901b3 100644 --- a/api/src/main/java/com/discordsrv/api/event/bus/EventBus.java +++ b/api/src/main/java/com/discordsrv/api/event/bus/EventBus.java @@ -35,7 +35,7 @@ public interface EventBus { /** * Subscribes the provided event listener to this {@link EventBus}. - * @param eventListener a event listener with atleast one valid {@link Subscribe} method. + * @param eventListener a event listener with at least one valid {@link Subscribe} method. * * @throws IllegalArgumentException if the given listener does not contain any valid listeners */ diff --git a/api/src/main/java/com/discordsrv/api/event/bus/EventListener.java b/api/src/main/java/com/discordsrv/api/event/bus/EventListener.java index 6372bb42..a3a2fc2d 100644 --- a/api/src/main/java/com/discordsrv/api/event/bus/EventListener.java +++ b/api/src/main/java/com/discordsrv/api/event/bus/EventListener.java @@ -42,7 +42,7 @@ public interface EventListener { String className(); /** - * The name of the method for this event listener + * The name of the method for this event listener. * @return the method name for this event listener * @see Method#getName() */ diff --git a/api/src/main/java/com/discordsrv/api/event/bus/internal/EventStateHolder.java b/api/src/main/java/com/discordsrv/api/event/bus/internal/EventStateHolder.java index 8f3fe28f..656db59b 100644 --- a/api/src/main/java/com/discordsrv/api/event/bus/internal/EventStateHolder.java +++ b/api/src/main/java/com/discordsrv/api/event/bus/internal/EventStateHolder.java @@ -33,8 +33,12 @@ public final class EventStateHolder { public static final ThreadLocal CANCELLED = new ThreadLocal<>(); public static final ThreadLocal PROCESSED = new ThreadLocal<>(); - public static final EventListener FAKE_LISTENER = new FakeListener(); + /** + * Used to indicate an unknown event listener. + */ + public static final EventListener UNKNOWN_LISTENER = new FakeListener(); + @SuppressWarnings("ConstantConditions") private static class FakeListener implements EventListener { @Override diff --git a/api/src/main/java/com/discordsrv/api/event/events/Cancellable.java b/api/src/main/java/com/discordsrv/api/event/events/Cancellable.java index 62e0826d..73674590 100644 --- a/api/src/main/java/com/discordsrv/api/event/events/Cancellable.java +++ b/api/src/main/java/com/discordsrv/api/event/events/Cancellable.java @@ -61,7 +61,7 @@ public interface Cancellable extends Event { EventListener listener = EventStateHolder.CANCELLED.get(); if (listener == null) { throw new IllegalStateException("Event is not cancelled"); - } else if (listener == EventStateHolder.FAKE_LISTENER) { + } else if (listener == EventStateHolder.UNKNOWN_LISTENER) { return null; } diff --git a/api/src/main/java/com/discordsrv/api/event/events/Processable.java b/api/src/main/java/com/discordsrv/api/event/events/Processable.java index 9999272a..daa92302 100644 --- a/api/src/main/java/com/discordsrv/api/event/events/Processable.java +++ b/api/src/main/java/com/discordsrv/api/event/events/Processable.java @@ -57,7 +57,7 @@ public interface Processable extends Event { EventListener listener = EventStateHolder.PROCESSED.get(); if (listener == null) { throw new IllegalStateException("Event has not been processed"); - } else if (listener == EventStateHolder.FAKE_LISTENER) { + } else if (listener == EventStateHolder.UNKNOWN_LISTENER) { return null; } diff --git a/api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSendEvent.java b/api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSentEvent.java similarity index 50% rename from api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSendEvent.java rename to api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSentEvent.java index 65405d5b..e3806929 100644 --- a/api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSendEvent.java +++ b/api/src/main/java/com/discordsrv/api/event/events/message/send/game/AbstractGameMessageSentEvent.java @@ -23,59 +23,24 @@ package com.discordsrv.api.event.events.message.send.game; -import com.discordsrv.api.channel.GameChannel; -import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; -import com.discordsrv.api.event.events.Cancellable; -import com.discordsrv.api.event.events.Processable; +import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster; +import com.discordsrv.api.event.events.Event; import org.jetbrains.annotations.NotNull; -public abstract class AbstractGameMessageSendEvent implements Cancellable, Processable { +public abstract class AbstractGameMessageSentEvent implements Event { - private SendableDiscordMessage discordMessage; - private GameChannel targetChannel; - private boolean cancelled; - private boolean processed; + private final ReceivedDiscordMessageCluster discordMessage; - public AbstractGameMessageSendEvent(@NotNull SendableDiscordMessage discordMessage, @NotNull GameChannel targetChannel) { + public AbstractGameMessageSentEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) { this.discordMessage = discordMessage; - this.targetChannel = targetChannel; } - @NotNull - public SendableDiscordMessage getDiscordMessage() { + /** + * Gets the {@link ReceivedDiscordMessageCluster} containing the sent message(s). + * @return the message cluster + */ + public ReceivedDiscordMessageCluster getDiscordMessage() { return discordMessage; } - public void setDiscordMessage(@NotNull SendableDiscordMessage discordMessage) { - this.discordMessage = discordMessage; - } - - @NotNull - public GameChannel getTargetChannel() { - return targetChannel; - } - - public void setTargetChannel(@NotNull GameChannel targetChannel) { - this.targetChannel = targetChannel; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } - - @Override - public boolean isProcessed() { - return processed; - } - - @Override - public void markAsProcessed() { - this.processed = true; - } } diff --git a/api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSendEvent.java b/api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSentEvent.java similarity index 79% rename from api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSendEvent.java rename to api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSentEvent.java index 6f6604d6..506fb561 100644 --- a/api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSendEvent.java +++ b/api/src/main/java/com/discordsrv/api/event/events/message/send/game/ChatMessageSentEvent.java @@ -23,13 +23,12 @@ package com.discordsrv.api.event.events.message.send.game; -import com.discordsrv.api.channel.GameChannel; -import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; +import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster; import org.jetbrains.annotations.NotNull; -public class ChatMessageSendEvent extends AbstractGameMessageSendEvent { +public class ChatMessageSentEvent extends AbstractGameMessageSentEvent { - public ChatMessageSendEvent(@NotNull SendableDiscordMessage discordMessage, @NotNull GameChannel targetChannel) { - super(discordMessage, targetChannel); + public ChatMessageSentEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) { + super(discordMessage); } } diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index cbb8f2c3..009077de 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -37,7 +37,7 @@ import com.discordsrv.common.discord.details.DiscordConnectionDetailsImpl; import com.discordsrv.common.event.bus.EventBusImpl; import com.discordsrv.common.function.CheckedRunnable; import com.discordsrv.common.listener.DefaultChannelLookupListener; -import com.discordsrv.common.listener.DefaultChatListener; +import com.discordsrv.common.listener.DefaultGameChatListener; import com.discordsrv.common.logging.DependencyLoggingFilter; import com.discordsrv.common.logging.logger.backend.LoggingBackend; import com.discordsrv.common.placeholder.PlaceholderServiceImpl; @@ -249,7 +249,7 @@ public abstract class AbstractDiscordSRV() { + @Override public @Nullable GameChannel load(@NonNull String channelName) { GameChannelLookupEvent event = new GameChannelLookupEvent(null, channelName); diff --git a/common/src/main/java/com/discordsrv/common/console/Console.java b/common/src/main/java/com/discordsrv/common/console/Console.java index f2749900..9ec0d52f 100644 --- a/common/src/main/java/com/discordsrv/common/console/Console.java +++ b/common/src/main/java/com/discordsrv/common/console/Console.java @@ -28,5 +28,9 @@ public interface Console extends ICommandSender { return true; } + /** + * Gets the logging backend for the server/proxy. + * @return the {@link LoggingBackend} + */ LoggingBackend loggingBackend(); } diff --git a/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageClusterImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageClusterImpl.java new file mode 100644 index 00000000..ba3aeb7d --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageClusterImpl.java @@ -0,0 +1,55 @@ +package com.discordsrv.common.discord.api.message; + +import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage; +import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster; +import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class ReceivedDiscordMessageClusterImpl implements ReceivedDiscordMessageCluster { + + private final List messages; + + public ReceivedDiscordMessageClusterImpl(List messages) { + this.messages = messages; + } + + @Override + public List getMessages() { + return messages; + } + + @SuppressWarnings("unchecked") + @Override + public CompletableFuture deleteAll() { + CompletableFuture[] futures = new CompletableFuture[messages.size()]; + for (int i = 0; i < messages.size(); i++) { + futures[i] = messages.get(i).delete(); + } + + return CompletableFuture.allOf(futures); + } + + @SuppressWarnings("unchecked") + @Override + public CompletableFuture editAll(SendableDiscordMessage newMessage) { + CompletableFuture[] futures = new CompletableFuture[messages.size()]; + for (int i = 0; i < messages.size(); i++) { + futures[i] = messages.get(i).edit(newMessage); + } + + return CompletableFuture.allOf(futures) + .thenApply(v -> { + List messages = new ArrayList<>(); + for (CompletableFuture future : futures) { + // All the futures are done, so we're just going to get the results from all of them + messages.add( + future.join()); + } + + return new ReceivedDiscordMessageClusterImpl(messages); + }); + } +} diff --git a/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageImpl.java index f88203a9..d1e43626 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/message/ReceivedDiscordMessageImpl.java @@ -150,6 +150,18 @@ public class ReceivedDiscordMessageImpl extends SendableDiscordMessageImpl imple return textChannel; } + @Override + public CompletableFuture delete() { + DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null); + if (textChannel == null) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new UnknownChannelException()); + return future; + } + + return textChannel.deleteMessageById(getId()); + } + @Override public @NotNull CompletableFuture edit(SendableDiscordMessage message) { if (!isWebhookMessage() && message.isWebhookMessage()) { @@ -163,6 +175,6 @@ public class ReceivedDiscordMessageImpl extends SendableDiscordMessageImpl imple return future; } - return textChannel.editMessageById(textChannel.getId(), message); + return textChannel.editMessageById(getId(), message); } } diff --git a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java index 9fadd6cc..ab758da3 100644 --- a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java +++ b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java @@ -18,6 +18,7 @@ package com.discordsrv.common.discord.connection.jda; +import com.discordsrv.api.discord.api.entity.user.DiscordUser; import com.discordsrv.api.discord.connection.DiscordConnectionDetails; import com.discordsrv.api.event.bus.EventPriority; import com.discordsrv.api.event.bus.Subscribe; @@ -44,7 +45,9 @@ import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.events.DisconnectEvent; import net.dv8tion.jda.api.events.ShutdownEvent; import net.dv8tion.jda.api.events.StatusChangeEvent; +import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.requests.CloseCode; +import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.AllowedMentions; @@ -55,13 +58,20 @@ import net.dv8tion.jda.internal.utils.IOUtil; import okhttp3.OkHttpClient; import javax.security.auth.login.LoginException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; public class JDAConnectionManager implements DiscordConnectionManager { + private static final Map PRIVILEGED_INTENTS = new HashMap<>(); + + static { + PRIVILEGED_INTENTS.put(GatewayIntent.GUILD_MEMBERS, "Server Members Intent"); + PRIVILEGED_INTENTS.put(GatewayIntent.GUILD_PRESENCES, "Presence Intent"); + } + private final DiscordSRV discordSRV; private final ScheduledExecutorService gatewayPool; private final ScheduledExecutorService rateLimitPool; @@ -70,6 +80,10 @@ public class JDAConnectionManager implements DiscordConnectionManager { private JDA instance; private boolean detailsAccepted = true; + // Bot owner details + private final AtomicReference> botOwnerRequest = new AtomicReference<>(); + private long botOwnerRetrievalTime; + public JDAConnectionManager(DiscordSRV discordSRV) { this.discordSRV = discordSRV; this.gatewayPool = new ScheduledThreadPoolExecutor( @@ -80,11 +94,58 @@ public class JDAConnectionManager implements DiscordConnectionManager { 5, new CountingThreadFactory(Scheduler.THREAD_NAME_PREFIX + "JDA RateLimit #%s") ); - RestAction.setDefaultFailure(t -> discordSRV.logger().error("Callback failed", t)); + + // Set default failure handling + RestAction.setDefaultFailure(t -> { + if (t instanceof ErrorResponseException) { + ErrorResponse response = ((ErrorResponseException) t).getErrorResponse(); + if (response == ErrorResponse.MFA_NOT_ENABLED) { + withBotOwner(user -> { + discordSRV.logger().error("+----------------------------------------------->"); + discordSRV.logger().error("| Failed to complete a request:"); + discordSRV.logger().error("|"); + discordSRV.logger().error("| The Discord bot's owner needs to enable 2FA"); + discordSRV.logger().error("| on their Discord account due to a Discord"); + discordSRV.logger().error("| server requiring 2FA for moderation actions"); + if (user != null) { + discordSRV.logger().error("|"); + discordSRV.logger().error("| The Discord bot's owner is " + user.getAsTag()); + } + discordSRV.logger().error("|"); + discordSRV.logger().error("| You can view instructions for enabling 2FA here:"); + discordSRV.logger().error("| https://support.discord.com/hc/en-us/articles/219576828-Setting-up-Two-Factor-Authentication"); + discordSRV.logger().error("+----------------------------------------------->"); + }); + } + } + discordSRV.logger().error("Callback failed", t); + }); + + // Disable all mentions by default for safety AllowedMentions.setDefaultMentions(Collections.emptyList()); + discordSRV.eventBus().subscribe(this); } + private void withBotOwner(Consumer botOwnerConsumer) { + long currentTime = System.currentTimeMillis(); + + CompletableFuture request = botOwnerRequest.get(); + if (request != null && botOwnerRetrievalTime + TimeUnit.MINUTES.toMillis(5) < currentTime) { + request.whenComplete((user, t) -> botOwnerConsumer.accept(t != null ? null : user)); + return; + } + + botOwnerRetrievalTime = currentTime; + CompletableFuture future = instance.retrieveApplicationInfo() + .timeout(10, TimeUnit.SECONDS) + .map(applicationInfo -> (DiscordUser) new DiscordUserImpl(applicationInfo.getOwner())) + .submit(); + + botOwnerRequest.set(future); + future.whenComplete((user, t) -> botOwnerConsumer.accept(t != null ? null : user)); + } + @Subscribe(priority = EventPriority.LATE) public void onDSRVShuttingDown(DiscordSRVShuttingDownEvent event) { // This has a timeout @@ -93,6 +154,10 @@ public class JDAConnectionManager implements DiscordConnectionManager { @Subscribe(priority = EventPriority.EARLIEST) public void onPlaceholderLookup(PlaceholderLookupEvent event) { + if (event.isProcessed()) { + return; + } + Set newContext = new HashSet<>(); for (Object o : event.getContext()) { Object converted; @@ -142,7 +207,8 @@ public class JDAConnectionManager implements DiscordConnectionManager { @Subscribe public void onDisconnect(DisconnectEvent event) { - if (checkCode(event.getCloseCode())) { + CloseCode closeCode = event.getCloseCode(); + if (checkCode(closeCode)) { return; } @@ -152,19 +218,25 @@ public class JDAConnectionManager implements DiscordConnectionManager { throw new IllegalStateException("Could not get the close frame for a disconnect"); } + String message; if (closedByServer) { - CloseCode closeCode = event.getCloseCode(); String closeReason = frame.getCloseReason(); - discordSRV.logger().debug("[JDA] [Server] Disconnected due to " + message = "[JDA] [Server] Disconnected due to " + frame.getCloseCode() + ": " + (closeCode != null ? closeCode.getMeaning() - : (closeReason != null ? closeReason : "(Unknown close reason)"))); + : (closeReason != null ? closeReason : "(Unknown close reason)")); } else { - discordSRV.logger().debug("[JDA] [Client] Disconnected due to " + message = "[JDA] [Client] Disconnected due to " + frame.getCloseCode() + ": " - + frame.getCloseReason()); + + frame.getCloseReason(); + } + + if (closeCode != null && !closeCode.isReconnect()) { + discordSRV.logger().error(message); + } else { + discordSRV.logger().debug(message); } } @@ -177,10 +249,32 @@ public class JDAConnectionManager implements DiscordConnectionManager { if (closeCode == null) { return false; } else if (closeCode == CloseCode.DISALLOWED_INTENTS) { - // TODO + Set intents = discordSRV.discordConnectionDetails().getGatewayIntents(); + discordSRV.logger().error("+-------------------------------------->"); + discordSRV.logger().error("| Failed to connect to Discord:"); + discordSRV.logger().error("|"); + discordSRV.logger().error("| The Discord bot is lacking one or more"); + discordSRV.logger().error("| privileged intents listed below"); + discordSRV.logger().error("|"); + for (GatewayIntent intent : intents) { + String displayName = PRIVILEGED_INTENTS.get(intent); + if (displayName != null) { + discordSRV.logger().error("| " + displayName); + } + } + discordSRV.logger().error("|"); + discordSRV.logger().error("| Instructions for enabling privileged gateway intents:"); + discordSRV.logger().error("| 1. Go to https://discord.com/developers/applications"); + discordSRV.logger().error("| 2. Choose the bot you are using for DiscordSRV"); + discordSRV.logger().error("| - Keep in mind it will only be visible to the "); + discordSRV.logger().error("| Discord user who created the bot"); + discordSRV.logger().error("| 3. Go to the \"Bot\" tab"); + discordSRV.logger().error("| 4. Make sure the intents listed above are all enabled"); + discordSRV.logger().error("| 5. "); // TODO + discordSRV.logger().error("+-------------------------------------->"); return true; - } else if (closeCode.isReconnect()) { - // TODO + } else if (closeCode == CloseCode.AUTHENTICATION_FAILED) { + invalidToken(); return true; } return false; @@ -245,12 +339,7 @@ public class JDAConnectionManager implements DiscordConnectionManager { instance = jdaBuilder.build(); break; } catch (LoginException ignored) { - discordSRV.logger().error("+-------------------------------+"); - discordSRV.logger().error("| Failed to connect to Discord: |"); - discordSRV.logger().error("| |"); - discordSRV.logger().error("| The token provided in the |"); - discordSRV.logger().error("| " + ConnectionConfig.FILE_NAME + " is invalid |"); - discordSRV.logger().error("+-------------------------------+"); + invalidToken(); break; } catch (Throwable t) { discordSRV.logger().error(t); @@ -267,6 +356,20 @@ public class JDAConnectionManager implements DiscordConnectionManager { } } + private void invalidToken() { + discordSRV.logger().error("+------------------------------>"); + discordSRV.logger().error("| Failed to connect to Discord:"); + discordSRV.logger().error("|"); + discordSRV.logger().error("| The token provided in the"); + discordSRV.logger().error("| " + ConnectionConfig.FILE_NAME + " is invalid"); + discordSRV.logger().error("|"); + discordSRV.logger().error("| You can get the token for your bot from:"); + discordSRV.logger().error("| https://discord.com/developers/applications"); + discordSRV.logger().error("| - Keep in mind the bot is only visible to"); + discordSRV.logger().error("| the Discord user that created the bot"); + discordSRV.logger().error("+------------------------------>"); + } + @Override public CompletableFuture reconnect() { return CompletableFuture.runAsync(() -> { diff --git a/common/src/main/java/com/discordsrv/common/event/bus/EventBusImpl.java b/common/src/main/java/com/discordsrv/common/event/bus/EventBusImpl.java index b9ae0f21..959e7f2c 100644 --- a/common/src/main/java/com/discordsrv/common/event/bus/EventBusImpl.java +++ b/common/src/main/java/com/discordsrv/common/event/bus/EventBusImpl.java @@ -166,8 +166,9 @@ public class EventBusImpl implements EventBus { List states = new ArrayList<>(STATES.size()); for (Pair, ThreadLocal> entry : STATES) { if (entry.getKey().apply(event)) { + // If the state is already set before listeners, we mark it as being changed by a 'unknown' event listener states.add(true); - entry.getValue().set(EventStateHolder.FAKE_LISTENER); + entry.getValue().set(EventStateHolder.UNKNOWN_LISTENER); continue; } states.add(false); diff --git a/common/src/main/java/com/discordsrv/common/listener/DefaultChatListener.java b/common/src/main/java/com/discordsrv/common/listener/DefaultGameChatListener.java similarity index 66% rename from common/src/main/java/com/discordsrv/common/listener/DefaultChatListener.java rename to common/src/main/java/com/discordsrv/common/listener/DefaultGameChatListener.java index 1e71af57..efe146c4 100644 --- a/common/src/main/java/com/discordsrv/common/listener/DefaultChatListener.java +++ b/common/src/main/java/com/discordsrv/common/listener/DefaultGameChatListener.java @@ -19,32 +19,35 @@ package com.discordsrv.common.listener; import com.discordsrv.api.channel.GameChannel; +import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage; import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; import com.discordsrv.api.event.bus.EventPriority; import com.discordsrv.api.event.bus.Subscribe; import com.discordsrv.api.event.events.message.receive.game.ChatMessageReceiveEvent; -import com.discordsrv.api.event.events.message.send.game.ChatMessageSendEvent; +import com.discordsrv.api.event.events.message.send.game.ChatMessageSentEvent; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.minecraftodiscord.MinecraftToDiscordChatConfig; +import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageClusterImpl; import com.discordsrv.common.function.OrDefault; import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer; import net.kyori.adventure.text.Component; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; -public class DefaultChatListener extends AbstractListener { +public class DefaultGameChatListener extends AbstractListener { - public DefaultChatListener(DiscordSRV discordSRV) { + public DefaultGameChatListener(DiscordSRV discordSRV) { super(discordSRV); } @Subscribe(priority = EventPriority.LAST) public void onChatReceive(ChatMessageReceiveEvent event) { - if (checkProcessor(event) || checkCancellation(event)) { + if (checkProcessor(event) || checkCancellation(event) || !discordSRV.isReady()) { return; } @@ -64,30 +67,33 @@ public class DefaultChatListener extends AbstractListener { .addReplacement("%message%", DiscordSerializer.INSTANCE.serialize(message)) .build(); - discordSRV.eventBus().publish( - new ChatMessageSendEvent( - discordMessage, - gameChannel - ) - ); - } - - @Subscribe(priority = EventPriority.LAST) - public void onChatSend(ChatMessageSendEvent event) { - if (checkProcessor(event) || checkCancellation(event) || !discordSRV.isReady()) { - return; - } - - GameChannel channel = event.getTargetChannel(); - BaseChannelConfig channelConfig = discordSRV.channelConfig().get(channel); - List channelIds = channelConfig instanceof ChannelConfig ? ((ChannelConfig) channelConfig).channelIds : Collections.emptyList(); - if (channelIds.isEmpty()) { + List channelIds = channelConfig.get(cfg -> cfg instanceof ChannelConfig ? ((ChannelConfig) cfg).channelIds : null); + if (channelIds == null || channelIds.isEmpty()) { return; } + List> futures = new ArrayList<>(); for (String channelId : channelIds) { discordSRV.discordAPI().getTextChannelById(channelId).ifPresent(textChannel -> - textChannel.sendMessage(event.getDiscordMessage())); + futures.add(textChannel.sendMessage(discordMessage))); } + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .whenComplete((v, t) -> { + if (t != null) { + discordSRV.logger().error("Failed to deliver message to Discord", t); + return; + } + + List messages = new ArrayList<>(); + for (CompletableFuture future : futures) { + messages.add(future.join()); + } + + discordSRV.eventBus().publish( + new ChatMessageSentEvent( + new ReceivedDiscordMessageClusterImpl(messages))); + }); } + }