Automatically determine required intents for Modules, document privileged intents on config options

This commit is contained in:
Vankka 2024-06-15 17:49:55 +03:00
parent 97ea72f0c7
commit 8078f427a4
No known key found for this signature in database
GPG Key ID: 62E48025ED4E7EBB
20 changed files with 200 additions and 44 deletions

View File

@ -26,6 +26,8 @@ package com.discordsrv.api.discord.connection.details;
import com.discordsrv.api.discord.entity.JDAEntity;
import net.dv8tion.jda.api.requests.GatewayIntent;
import java.util.EnumSet;
public enum DiscordGatewayIntent implements JDAEntity<GatewayIntent> {
GUILD_MEMBERS(GatewayIntent.GUILD_MEMBERS, "Server Members Intent"),
@ -43,16 +45,31 @@ public enum DiscordGatewayIntent implements JDAEntity<GatewayIntent> {
DIRECT_MESSAGE_TYPING(GatewayIntent.DIRECT_MESSAGE_TYPING),
MESSAGE_CONTENT(GatewayIntent.MESSAGE_CONTENT, "Message Content Intent"),
SCHEDULED_EVENTS(GatewayIntent.SCHEDULED_EVENTS),
AUTO_MODERATION_CONFIGURATION(GatewayIntent.AUTO_MODERATION_CONFIGURATION),
AUTO_MODERATION_EXECUTION(GatewayIntent.AUTO_MODERATION_EXECUTION),
;
static DiscordGatewayIntent getByJda(GatewayIntent jda) {
public static final EnumSet<DiscordGatewayIntent> PRIVILEGED;
static {
EnumSet<DiscordGatewayIntent> privileged = EnumSet.noneOf(DiscordGatewayIntent.class);
for (DiscordGatewayIntent intent : values()) {
if (intent.privileged()) {
privileged.add(intent);
}
}
PRIVILEGED = privileged;
}
public static DiscordGatewayIntent getByJda(GatewayIntent jda) {
for (DiscordGatewayIntent value : values()) {
if (value.asJDA() == jda) {
return value;
}
}
throw new IllegalArgumentException("This intent does not have a ");
throw new IllegalArgumentException("This intent does not have a DiscordGatewayIntent");
}
private final GatewayIntent jda;

View File

@ -28,6 +28,8 @@ import net.dv8tion.jda.api.events.GenericEvent;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* DiscordSRV's event bus, handling all events extending {@link Event}s and {@link GenericEvent}s.
*/
@ -48,6 +50,14 @@ public interface EventBus {
*/
void unsubscribe(@NotNull Object eventListener);
/**
* Gets the listeners for a given event listener.
*
* @param eventListener an event listener that has valid {@link Subscribe} methods.
* @return a set of event listener in the provided class according to this {@link EventBus}
*/
Collection<? extends EventListener> getListeners(@NotNull Object eventListener);
/**
* Publishes a DiscordSRV {@link Event} to this {@link EventBus}.
*

View File

@ -28,7 +28,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
/**
* A event listener.
* An event listener.
*/
@SuppressWarnings("unused") // API
public interface EventListener {
@ -49,4 +49,11 @@ public interface EventListener {
@NotNull
String methodName();
/**
* The event this listener is listening to.
* @return the event extending {@link com.discordsrv.api.event.events.Event} or {@link net.dv8tion.jda.api.events.GenericEvent}
*/
@NotNull
Class<?> eventClass();
}

View File

@ -50,5 +50,10 @@ public final class EventStateHolder {
public @NotNull String methodName() {
return null;
}
@Override
public @NotNull Class<?> eventClass() {
return null;
}
}
}

View File

@ -27,10 +27,14 @@ import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.discord.connection.details.DiscordCacheFlag;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.discord.connection.details.DiscordMemberCachePolicy;
import com.discordsrv.api.event.bus.EventListener;
import com.discordsrv.api.event.events.discord.message.AbstractDiscordMessageEvent;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.events.guild.member.GenericGuildMemberEvent;
import net.dv8tion.jda.api.requests.GatewayIntent;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.*;
import java.util.function.Consumer;
public interface Module {
@ -46,13 +50,57 @@ public interface Module {
/**
* Provides a {@link Collection} of {@link DiscordGatewayIntent}s that are required for this {@link Module}.
* This defaults to determining intents based on the events listened to in this class via {@link com.discordsrv.api.event.bus.Subscribe} methods.
* @return the collection of gateway intents required by this module at the time this method is called
*/
@SuppressWarnings("unchecked") // Class generic cast
@NotNull
default Collection<DiscordGatewayIntent> requiredIntents() {
DiscordSRVApi api = DiscordSRVApi.get();
if (api == null) {
return Collections.emptyList();
}
Collection<? extends EventListener> listeners = api.eventBus().getListeners(this);
EnumSet<DiscordGatewayIntent> intents = EnumSet.noneOf(DiscordGatewayIntent.class);
for (EventListener listener : listeners) {
Class<?> eventClass = listener.eventClass();
// DiscordSRV
if (AbstractDiscordMessageEvent.class.isAssignableFrom(eventClass)) {
intents.addAll(EnumSet.of(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.DIRECT_MESSAGES));
}
if (GenericGuildMemberEvent.class.isAssignableFrom(eventClass)) {
intents.add(DiscordGatewayIntent.GUILD_MEMBERS);
}
// JDA
if (!GenericEvent.class.isAssignableFrom(eventClass)) {
continue;
}
EnumSet<GatewayIntent> jdaIntents = GatewayIntent.fromEvents((Class<? extends GenericEvent>) listener.eventClass());
for (GatewayIntent jdaIntent : jdaIntents) {
try {
intents.add(DiscordGatewayIntent.getByJda(jdaIntent));
} catch (IllegalArgumentException ignored) {}
}
}
// Below are some intents that will not be added by event reference (and have to be specified intentionally)
// Direct messages are rarely used by bots (and Guild & Direct message events are the same)
intents.remove(DiscordGatewayIntent.DIRECT_MESSAGES);
intents.remove(DiscordGatewayIntent.DIRECT_MESSAGE_REACTIONS);
intents.remove(DiscordGatewayIntent.DIRECT_MESSAGE_TYPING);
// Presences change A LOT
intents.remove(DiscordGatewayIntent.GUILD_PRESENCES);
return intents;
}
/**
* Provides a {@link Collection} of {@link DiscordCacheFlag}s that are required for this {@link Module}.
* {@link DiscordGatewayIntent}s required by the cache flags will be required automatically.

View File

@ -91,7 +91,8 @@ public class ConsoleConfig {
);
}
@Comment("If command execution is enabled")
@Comment("If command execution in this console channel is enabled\n"
+ "Requires the \"Message Content Intent\"")
public boolean enabled = true;
@Comment("At least one condition has to match to allow execution")

View File

@ -27,13 +27,16 @@ import java.util.List;
@ConfigSerializable
public class MemberCachingConfig {
@Comment("If linked users' members should be cached, this requires the \"Server Members Intent\"")
@Comment("If linked users' members should be cached\n"
+ "Requires the \"Server Members Intent\"")
public boolean linkedUsers = true;
@Comment("If all members should be cached, this requires the \"Server Members Intent\"")
@Comment("If all members should be cached\n"
+ "Requires the \"Server Members Intent\"")
public boolean all = false;
@Comment("If members should be cached at startup, this requires the \"Server Members Intent\"")
@Comment("If members should be cached at startup\n"
+ "Requires the \"Server Members Intent\"")
public boolean chunk = true;
@Comment("Filter for which servers should be cached at startup")

View File

@ -31,6 +31,7 @@ import java.util.regex.Pattern;
@ConfigSerializable
public class DiscordToMinecraftChatConfig {
@Comment("Requires the \"Message Content Intent\"")
public boolean enabled = true;
@Comment("The Discord to Minecraft message format for regular users and bots")

View File

@ -70,7 +70,8 @@ public class MinecraftToDiscordChatConfig implements IMessageConfig {
public boolean channels = true;
@Comment("If user mentions should be rendered on Discord\n"
+ "The player needs the discordsrv.mention.user permission to trigger a notification")
+ "The player needs the discordsrv.mention.user permission to trigger a notification\n"
+ "Requires the \"Server Members Intent\"")
public boolean users = true;
@Comment("If uncached users should be looked up from the Discord API when a mention (\"@something\") occurs in chat.\n"

View File

@ -25,6 +25,7 @@ import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class MirroringConfig {
@Comment("Requires the \"Message Content Intent\"")
public boolean enabled = true;
@Comment("Users, bots, roles and webhooks to ignore when mirroring")

View File

@ -28,6 +28,7 @@ public class BaseChannelConfig {
@Order(0)
public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig();
@Order(0)
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();

View File

@ -2,6 +2,8 @@ package com.discordsrv.common.console;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.discord.message.DiscordMessageReceiveEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.ConsoleConfig;
import com.discordsrv.common.config.main.generic.DestinationConfig;
@ -15,10 +17,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
public class ConsoleModule extends AbstractModule<DiscordSRV> implements LogAppender {
@ -32,7 +31,12 @@ public class ConsoleModule extends AbstractModule<DiscordSRV> implements LogAppe
@Override
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
return Collections.singletonList(DiscordGatewayIntent.MESSAGE_CONTENT);
boolean anyExecutors = discordSRV.config().console.stream().anyMatch(config -> config.commandExecution.enabled);
if (anyExecutors) {
return EnumSet.of(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
}
return Collections.emptySet();
}
@Override
@ -83,4 +87,11 @@ public class ConsoleModule extends AbstractModule<DiscordSRV> implements LogAppe
handler.queue(entry);
}
}
@Subscribe
public void onDiscordMessageReceived(DiscordMessageReceiveEvent event) {
for (SingleConsoleHandler handler : handlers) {
handler.handleDiscordMessageReceived(event);
}
}
}

View File

@ -9,7 +9,6 @@ import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.util.DiscordFormattingUtil;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.discord.message.DiscordMessageReceiveEvent;
import com.discordsrv.api.placeholder.PlainPlaceholderFormat;
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
@ -67,11 +66,13 @@ public class SingleConsoleHandler {
this.messageCache = config.appender.useEditing ? new ArrayList<>() : null;
timeQueueProcess();
discordSRV.eventBus().subscribe(this);
}
@Subscribe
public void onDiscordMessageReceived(DiscordMessageReceiveEvent event) {
public void handleDiscordMessageReceived(DiscordMessageReceiveEvent event) {
if (!config.commandExecution.enabled) {
return;
}
DiscordMessageChannel messageChannel = event.getChannel();
DiscordGuildChannel channel = messageChannel instanceof DiscordGuildChannel ? (DiscordGuildChannel) messageChannel : null;
if (channel == null) {
@ -174,7 +175,6 @@ public class SingleConsoleHandler {
@SuppressWarnings("SynchronizeOnNonFinalField")
public void shutdown() {
shutdown = true;
discordSRV.eventBus().unsubscribe(this);
queueProcessingFuture.cancel(false);
try {
synchronized (queueProcessingFuture) {

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.discord.api;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
@ -37,6 +38,7 @@ import com.discordsrv.api.event.events.discord.member.role.DiscordMemberRoleRemo
import com.discordsrv.api.event.events.discord.message.DiscordMessageDeleteEvent;
import com.discordsrv.api.event.events.discord.message.DiscordMessageReceiveEvent;
import com.discordsrv.api.event.events.discord.message.DiscordMessageUpdateEvent;
import com.discordsrv.api.module.Module;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.discord.api.entity.component.DiscordInteractionHookImpl;
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
@ -59,10 +61,9 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback;
import net.dv8tion.jda.api.interactions.commands.Command;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -76,6 +77,15 @@ public class DiscordAPIEventModule extends AbstractModule<DiscordSRV> {
return discordSRV.discordAPI();
}
/**
* See {@link Module#requiredIntents()} implementation.
* @return no intents as these events might not be listened to
*/
@Override
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
return Collections.emptyList();
}
@Subscribe
public void onMessageReceived(MessageReceivedEvent event) {
discordSRV.eventBus().publish(new DiscordMessageReceiveEvent(

View File

@ -40,10 +40,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
@ -78,6 +75,28 @@ public class EventBusImpl implements EventBus {
throw new IllegalArgumentException("Listener is already registered");
}
Pair<List<EventListenerImpl>, List<Throwable>> parsed = parseListeners(eventListener);
List<Throwable> suppressedMethods = parsed.getValue();
List<EventListenerImpl> methods = parsed.getKey();
if (methods.isEmpty() || !suppressedMethods.isEmpty()) {
IllegalArgumentException exception = new IllegalArgumentException(eventListener.getClass().getName()
+ " doesn't have valid listener methods that are annotated with " + Subscribe.class.getName());
suppressedMethods.forEach(exception::addSuppressed);
throw exception;
}
listeners.put(eventListener, methods);
allListeners.addAll(methods);
logger.debug("Listener " + eventListener.getClass().getName() + " subscribed");
}
@Override
public Collection<EventListenerImpl> getListeners(@NotNull Object eventListener) {
return parseListeners(eventListener).getKey();
}
private Pair<List<EventListenerImpl>, List<Throwable>> parseListeners(Object eventListener) {
Class<?> listenerClass = eventListener.getClass();
List<Throwable> suppressedMethods = new ArrayList<>();
@ -90,16 +109,7 @@ public class EventBusImpl implements EventBus {
}
} while ((currentClass = currentClass.getSuperclass()) != null);
if (methods.isEmpty() || !suppressedMethods.isEmpty()) {
IllegalArgumentException exception = new IllegalArgumentException(listenerClass.getName()
+ " doesn't have valid listener methods that are annotated with " + Subscribe.class.getName());
suppressedMethods.forEach(exception::addSuppressed);
throw exception;
}
listeners.put(eventListener, methods);
allListeners.addAll(methods);
logger.debug("Listener " + eventListener.getClass().getName() + " subscribed");
return Pair.of(methods, suppressedMethods);
}
private void checkMethod(Object eventListener, Class<?> listenerClass, Method method,
@ -215,6 +225,9 @@ public class EventBusImpl implements EventBus {
if (eventListener.className().startsWith("com.discordsrv")) {
logger.error("Failed to pass " + eventClassName + " to " + eventListener, cause);
} else {
// Print the listener failing without references to the DiscordSRV event bus
// as it isn't relevant to the exception, and often causes users to suspect DiscordSRV is doing something wrong when it isn't
//noinspection CallToPrintStackTrace
e.getCause().printStackTrace();
}
TestHelper.fail(cause);

View File

@ -53,7 +53,8 @@ public class EventListenerImpl implements EventListener {
return listener;
}
public Class<?> eventClass() {
@Override
public @NotNull Class<?> eventClass() {
return eventClass;
}

View File

@ -39,6 +39,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Consumer;
@ -56,10 +57,10 @@ public class DiscordInviteModule extends AbstractModule<DiscordSRV> {
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
DiscordInviteConfig config = discordSRV.config().invite;
if (StringUtils.isNotEmpty(config.inviteUrl)) {
return Collections.emptyList();
return Collections.emptySet();
}
return Collections.singleton(DiscordGatewayIntent.GUILD_INVITES);
return EnumSet.of(DiscordGatewayIntent.GUILD_INVITES);
}
@Subscribe

View File

@ -49,8 +49,8 @@ import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
@ -92,7 +92,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
@Override
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
return Arrays.asList(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
return EnumSet.of(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
}
@Subscribe

View File

@ -83,7 +83,7 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
@Override
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
return Arrays.asList(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
return EnumSet.of(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
}
@SuppressWarnings("unchecked") // Wacky generics

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.messageforwarding.game.minecrafttodiscord;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
@ -38,6 +39,7 @@ import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameE
import net.dv8tion.jda.api.events.role.RoleCreateEvent;
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@ -57,6 +59,29 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
super(discordSRV);
}
@Override
public @NotNull Collection<DiscordGatewayIntent> requiredIntents() {
boolean anyUserMentions = false;
for (BaseChannelConfig channel : discordSRV.channelConfig().getAllChannels()) {
MinecraftToDiscordChatConfig config = channel.minecraftToDiscord;
if (!config.enabled) {
continue;
}
MinecraftToDiscordChatConfig.Mentions mentions = config.mentions;
if (mentions.users) {
anyUserMentions = true;
break;
}
}
if (anyUserMentions) {
return EnumSet.of(DiscordGatewayIntent.GUILD_MEMBERS);
}
return Collections.emptySet();
}
@Override
public boolean isEnabled() {
for (BaseChannelConfig channel : discordSRV.channelConfig().getAllChannels()) {