diff --git a/application/config.json.template b/application/config.json.template index a1aec8f470..009ab04a5f 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -115,5 +115,9 @@ "fallbackChannelPattern": "java-news-and-changes", "pollIntervalInMinutes": 10 }, - "memberCountCategoryPattern": "Info" + "memberCountCategoryPattern": "Info", + "dynamicVoiceChannelPattern": [ + "Gaming", + "Chit Chat" + ] } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 54ba5bef5e..30fc57e271 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -148,7 +148,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new PinnedNotificationRemover(config)); // Voice receivers - features.add(new DynamicVoiceListener()); + features.add(new DynamicVoiceListener(config)); // Event receivers features.add(new RejoinModerationRoleListener(actionsStore, config)); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/dynamicvc/DynamicVoiceListener.java b/application/src/main/java/org/togetherjava/tjbot/features/dynamicvc/DynamicVoiceListener.java index 60f1b10f3a..e359d494a3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/dynamicvc/DynamicVoiceListener.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/dynamicvc/DynamicVoiceListener.java @@ -1,14 +1,141 @@ package org.togetherjava.tjbot.features.dynamicvc; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.features.VoiceReceiverAdapter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.Stream; + public class DynamicVoiceListener extends VoiceReceiverAdapter { + private final Map> patterns = new HashMap<>(); + private static final Pattern channelTopicPattern = Pattern.compile("(\\s+\\d+)$"); + + public DynamicVoiceListener(Config config) { + config.getDynamicVoiceChannelPatterns() + .forEach(pattern -> patterns.put(pattern, Pattern.compile(pattern).asMatchPredicate())); + } + @Override public void onVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) { - // TODO: Complete + AudioChannelUnion joinChannel = event.getChannelJoined(); + AudioChannelUnion leftChannel = event.getChannelLeft(); + + if (joinChannel != null && leftChannel != null) { + if (!getChannelTopic(joinChannel.getName()) + .equals(getChannelTopic(leftChannel.getName()))) { + handleTopicUpdate(event, joinChannel); + handleTopicUpdate(event, leftChannel); + return; + } + } + + if (joinChannel != null) { + handleTopicUpdate(event, joinChannel); + } else if (leftChannel != null) { + handleTopicUpdate(event, leftChannel); + } + } + + private void handleTopicUpdate(GuildVoiceUpdateEvent event, AudioChannelUnion channel) { + if (channel == null) { + return; + } + + Guild guild = event.getGuild(); + String channelTopic = getChannelTopic(channel.getName()); + + if (patterns.get(channelTopic) == null) { + return; + } + + long emptyChannelsCount = getEmptyChannelsCountFromTopic(guild, channelTopic); + + if (emptyChannelsCount == 0) { + createVoiceChannelFromTopic(channel, getChannelCountFromTopic(guild, channelTopic)); + } else if (emptyChannelsCount != 1) { + removeDuplicateEmptyChannels(guild, channelTopic); + } + } + + private static void createVoiceChannelFromTopic(AudioChannelUnion originalChannel, + long topicChannelsCount) { + String channelTopic = getChannelTopic(originalChannel.getName()); + + originalChannel.createCopy() + .setName(getNumberedChannelTopic(channelTopic, topicChannelsCount + 1)) + .setPosition(originalChannel.getPositionRaw()) + .queue(); + + } + + private void removeDuplicateEmptyChannels(Guild guild, String channelTopic) { + List channelsToRemove = getVoiceChannelsFromTopic(guild, channelTopic) + .filter(channel -> channel.getMembers().isEmpty()) + .toList(); + + channelsToRemove.subList(1, channelsToRemove.size()) + .forEach(channel -> channel.delete().queue(success -> { + List channels = + getVoiceChannelsFromTopic(guild, channelTopic).toList(); + + IntStream.range(0, channels.size()) + .asLongStream() + .mapToObj(number -> Pair.of(number + 1, channels.get((int) number))) + .filter(pair -> pair.getLeft() != 1) + .forEach(pair -> { + long number = pair.getLeft(); + VoiceChannel voiceChannel = pair.getRight(); + String voiceChannelNameTopic = getChannelTopic(voiceChannel.getName()); + + voiceChannel.getManager() + .setName(getNumberedChannelTopic(voiceChannelNameTopic, number)) + .queue(); + }); + })); + } + + private long getChannelCountFromTopic(Guild guild, String channelTopic) { + return getVoiceChannelsFromTopic(guild, channelTopic).count(); + } + + private Stream getVoiceChannelsFromTopic(Guild guild, String channelTopic) { + return guild.getVoiceChannels() + .stream() + .filter(channel -> patterns.get(channelTopic).test(getChannelTopic(channel.getName()))); + } + + private long getEmptyChannelsCountFromTopic(Guild guild, String channelTopic) { + return getVoiceChannelsFromTopic(guild, channelTopic) + .map(channel -> channel.getMembers().size()) + .filter(number -> number == 0) + .count(); + } + + private static String getChannelTopic(String channelName) { + Matcher matcher = channelTopicPattern.matcher(channelName); + + if (matcher.find()) { + return matcher.replaceAll(""); + } + + return channelName; + } + + private static String getNumberedChannelTopic(String channelTopic, long id) { + return String.format("%s %d", channelTopic, id); } }