Improve docs, change game chat events a bit, add in large errors for certain things going wrong with the Discord connection

This commit is contained in:
Vankka 2021-08-26 20:07:16 +03:00
parent ff8385dfda
commit a8cce7cf8d
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
19 changed files with 313 additions and 105 deletions

View File

@ -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<ReceivedDiscordMessage> 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<Void> 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

View File

@ -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<Void> 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<ReceivedDiscordMessage> edit(SendableDiscordMessage message);

View File

@ -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<? extends ReceivedDiscordMessage> getMessages();
/**
* Deletes all the messages from this cluster, one request per message.
* @return a future that fails if any of the requests fail.
*/
CompletableFuture<Void> 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<ReceivedDiscordMessageCluster> editAll(SendableDiscordMessage newMessage);
}

View File

@ -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();
}
}

View File

@ -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
*/

View File

@ -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()
*/

View File

@ -33,8 +33,12 @@ public final class EventStateHolder {
public static final ThreadLocal<EventListener> CANCELLED = new ThreadLocal<>();
public static final ThreadLocal<EventListener> 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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<C extends MainConfig, CC extends Connec
// Register listeners
// Chat
eventBus().subscribe(new DefaultChannelLookupListener(this));
eventBus().subscribe(new DefaultChatListener(this));
eventBus().subscribe(new DefaultGameChatListener(this));
}
@OverridingMethodsMustInvokeSuper

View File

@ -39,8 +39,11 @@ public class ChannelConfig {
public ChannelConfig(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.channels = discordSRV.caffeineBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.expireAfterWrite(60, TimeUnit.SECONDS)
.expireAfterAccess(30, TimeUnit.SECONDS)
.refreshAfterWrite(10, TimeUnit.SECONDS)
.build(new CacheLoader<String, GameChannel>() {
@Override
public @Nullable GameChannel load(@NonNull String channelName) {
GameChannelLookupEvent event = new GameChannelLookupEvent(null, channelName);

View File

@ -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();
}

View File

@ -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<ReceivedDiscordMessage> messages;
public ReceivedDiscordMessageClusterImpl(List<ReceivedDiscordMessage> messages) {
this.messages = messages;
}
@Override
public List<ReceivedDiscordMessage> getMessages() {
return messages;
}
@SuppressWarnings("unchecked")
@Override
public CompletableFuture<Void> deleteAll() {
CompletableFuture<Void>[] 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<ReceivedDiscordMessageCluster> editAll(SendableDiscordMessage newMessage) {
CompletableFuture<ReceivedDiscordMessage>[] 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<ReceivedDiscordMessage> messages = new ArrayList<>();
for (CompletableFuture<ReceivedDiscordMessage> 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);
});
}
}

View File

@ -150,6 +150,18 @@ public class ReceivedDiscordMessageImpl extends SendableDiscordMessageImpl imple
return textChannel;
}
@Override
public CompletableFuture<Void> delete() {
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
if (textChannel == null) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(new UnknownChannelException());
return future;
}
return textChannel.deleteMessageById(getId());
}
@Override
public @NotNull CompletableFuture<ReceivedDiscordMessage> 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);
}
}

View File

@ -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<GatewayIntent, String> 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<CompletableFuture<DiscordUser>> 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<DiscordUser> botOwnerConsumer) {
long currentTime = System.currentTimeMillis();
CompletableFuture<DiscordUser> 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<DiscordUser> 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<Object> 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<GatewayIntent> 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<Void> reconnect() {
return CompletableFuture.runAsync(() -> {

View File

@ -166,8 +166,9 @@ public class EventBusImpl implements EventBus {
List<Boolean> states = new ArrayList<>(STATES.size());
for (Pair<Function<Object, Boolean>, ThreadLocal<EventListener>> 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);

View File

@ -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<String> channelIds = channelConfig instanceof ChannelConfig ? ((ChannelConfig) channelConfig).channelIds : Collections.emptyList();
if (channelIds.isEmpty()) {
List<String> channelIds = channelConfig.get(cfg -> cfg instanceof ChannelConfig ? ((ChannelConfig) cfg).channelIds : null);
if (channelIds == null || channelIds.isEmpty()) {
return;
}
List<CompletableFuture<ReceivedDiscordMessage>> 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<ReceivedDiscordMessage> messages = new ArrayList<>();
for (CompletableFuture<ReceivedDiscordMessage> future : futures) {
messages.add(future.join());
}
discordSRV.eventBus().publish(
new ChatMessageSentEvent(
new ReceivedDiscordMessageClusterImpl(messages)));
});
}
}