diff --git a/src/de/steamwar/bungeecore/bot/ChannelManager.java b/src/de/steamwar/bungeecore/bot/ChannelManager.java new file mode 100644 index 00000000..28719018 --- /dev/null +++ b/src/de/steamwar/bungeecore/bot/ChannelManager.java @@ -0,0 +1,189 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bungeecore.bot; + +import lombok.Setter; +import lombok.experimental.Accessors; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.restaction.ChannelAction; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.IntFunction; + +public class ChannelManager extends ListenerAdapter { + + private static final String[] ROMEN_NUMERALS = { + "", + "I", + "II", + "III", + "IV", + "V", + "VI", + "VII", + "VIII", + "IX", + "X", + "XI", + "XII", + "XIII", + "XIV", + "XV", + "XVI", + "XVII", + "XVIII", + "XIX", + "XX", + }; + + private ChannelManagerOptions options; + private Guild guild; + + private List channelNames = new ArrayList<>(); + private List channels = new ArrayList<>(); + + public ChannelManager(ChannelManagerOptions options) { + options.check(); + this.options = options; + + JDA jda = options.jda; + jda.addEventListener(this); + guild = jda.getGuildById(options.guildID); + if (guild == null) { + throw new IllegalArgumentException("Guild with ID " + options.guildID + " not found"); + } + + for (int i = 1; i <= options.maxChannels; i++) { + channelNames.add(options.channelName.apply(i)); + } + + startUp(); + } + + private void startUp() { + Objects.requireNonNull(guild.getCategoryById(options.categoryID)) + .getVoiceChannels() + .stream() + .filter(voiceChannel -> channelNames.contains(voiceChannel.getName())) + .forEach(channels::add); + channels.sort((o1, o2) -> { + int i1 = channelNames.indexOf(o1.getName()); + int i2 = channelNames.indexOf(o2.getName()); + return Integer.compare(i1, i2); + }); + + if (channels.isEmpty()) { + ChannelAction channelAction = guild.createVoiceChannel(channelNames.get(0)) + .setParent(guild.getCategoryById(options.categoryID)); + if (options.channelCreator != null) { + options.channelCreator.accept(channelAction); + } + VoiceChannel newChannel = channelAction.complete(); + channels.add(newChannel); + return; + } + + if (channels.size() == 1) { + // TODO: Check if channel is not empty and create new channel + } + } + + @Override + public synchronized void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) { + if (event.getChannelLeft() instanceof VoiceChannel) { + leave((VoiceChannel) event.getChannelLeft()); + } + if (event.getChannelJoined() instanceof VoiceChannel) { + join((VoiceChannel) event.getChannelJoined()); + } + } + + private void leave(VoiceChannel voiceChannel) { + if (voiceChannel.getGuild().getIdLong() != options.guildID) return; + if (voiceChannel.getParentCategoryIdLong() != options.categoryID) return; + if (!channelNames.contains(voiceChannel.getName())) return; + if (!voiceChannel.getMembers().isEmpty()) return; + if (channels.size() <= 1) return; + + int index = channels.indexOf(voiceChannel); + boolean needsRecreate = channels.size() == options.maxChannels && index < options.maxChannels - 1 && !channels.get(channels.size() - 1).getMembers().isEmpty(); + channels.remove(index); + voiceChannel.delete().complete(); + for (int i = index; i < channels.size(); i++) { + VoiceChannel channel = channels.get(i); + channel.getManager().setName(options.channelName.apply(i + 1)).queue(); + } + if (needsRecreate) { + createChannelWithIndex(channels.size()); + } + } + + private void join(VoiceChannel voiceChannel) { + if (voiceChannel.getGuild().getIdLong() != options.guildID) return; + if (voiceChannel.getParentCategoryIdLong() != options.categoryID) return; + if (!channelNames.contains(voiceChannel.getName())) return; + int index = channels.indexOf(voiceChannel); + if (index < channels.size() - 1) return; + if (index >= options.maxChannels - 1) return; + + createChannelWithIndex(index + 1); + } + + private void createChannelWithIndex(int index) { + ChannelAction channelAction = guild.createVoiceChannel(channelNames.get(index)) + .setParent(guild.getCategoryById(options.categoryID)); + if (options.channelCreator != null) { + options.channelCreator.accept(channelAction); + } + VoiceChannel newChannel = channelAction.complete(); + channels.add(newChannel); + } + + @Setter + @Accessors(fluent = true) + public static final class ChannelManagerOptions { + private JDA jda; + private long guildID; + private long categoryID; + private int maxChannels = 20; + private IntFunction channelName = USER_LOUNGE; + private Consumer> channelCreator = null; + + private void check() { + if (jda == null) throw new IllegalStateException("JDA is null"); + if (guildID == 0) throw new IllegalStateException("GuildID is 0"); + if (categoryID == 0) throw new IllegalStateException("CategoryID is 0"); + if (maxChannels < 1) throw new IllegalStateException("MaxChannels is less than 1"); + if (maxChannels > 20) throw new IllegalStateException("MaxChannels is greater than 20"); + if (channelName == null) throw new IllegalStateException("ChannelName is null"); + } + } + + public static final IntFunction USER_LOUNGE = i -> "「\uD83D\uDCAC」User Lounge " + ROMEN_NUMERALS[i]; + public static final IntFunction TEAM_LOUNGE = i -> "「\uD83D\uDCAC」Team Lounge " + ROMEN_NUMERALS[i]; +} diff --git a/src/de/steamwar/bungeecore/bot/SteamwarDiscordBot.java b/src/de/steamwar/bungeecore/bot/SteamwarDiscordBot.java index 0900437d..19958e42 100644 --- a/src/de/steamwar/bungeecore/bot/SteamwarDiscordBot.java +++ b/src/de/steamwar/bungeecore/bot/SteamwarDiscordBot.java @@ -38,11 +38,12 @@ import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction; import net.dv8tion.jda.api.utils.MemberCachePolicy; +import net.dv8tion.jda.api.utils.cache.CacheFlag; import net.md_5.bungee.api.ProxyServer; -import javax.security.auth.login.LoginException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -74,8 +75,10 @@ public class SteamwarDiscordBot { public SteamwarDiscordBot() { INSTANCE = this; JDABuilder builder = JDABuilder.createDefault(SteamwarDiscordBotConfig.TOKEN); + builder.enableIntents(GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.GUILD_EMOJIS_AND_STICKERS); builder.setStatus(OnlineStatus.ONLINE); - builder.setMemberCachePolicy(MemberCachePolicy.ONLINE); + builder.enableCache(CacheFlag.VOICE_STATE); + builder.setMemberCachePolicy(MemberCachePolicy.ONLINE.or(MemberCachePolicy.VOICE)); jda = builder.build(); ProxyServer.getInstance().getScheduler().runAsync(BungeeCore.get(), () -> { try { @@ -88,6 +91,14 @@ public class SteamwarDiscordBot { } catch (Exception e) { BungeeCore.get().getLogger().log(Level.SEVERE, "Could not set initial activity to discord", e); } + + new ChannelManager(new ChannelManager.ChannelManagerOptions() + .guildID(869612801499476068L) + .categoryID(869612801520435312L) + .maxChannels(3) + .jda(jda) + ); + EventManager.update(); SchematicsManager.update(); ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), () -> { @@ -113,16 +124,20 @@ public class SteamwarDiscordBot { new SlashCommandListener(); jda.retrieveCommands().complete().forEach(command -> jda.deleteCommandById(command.getId()).queue()); - Guild guild = jda.getGuildById(SteamwarDiscordBotConfig.GUILD); - guild.retrieveCommands().complete().forEach(command -> guild.deleteCommandById(command.getId()).complete()); - CommandListUpdateAction commands = jda.getGuildById(SteamwarDiscordBotConfig.GUILD).updateCommands(); - addCommand(commands, new MuteCommand()); - addCommand(commands, new BanCommand()); - addCommand(commands, new WhoisCommand()); - addCommand(commands, new TeamCommand()); - addCommand(commands, new ListCommand()); - addCommand(commands, new UnbanCommand()); - commands.complete(); + try { + Guild guild = jda.getGuildById(SteamwarDiscordBotConfig.GUILD); + guild.retrieveCommands().complete().forEach(command -> guild.deleteCommandById(command.getId()).complete()); + CommandListUpdateAction commands = jda.getGuildById(SteamwarDiscordBotConfig.GUILD).updateCommands(); + addCommand(commands, new MuteCommand()); + addCommand(commands, new BanCommand()); + addCommand(commands, new WhoisCommand()); + addCommand(commands, new TeamCommand()); + addCommand(commands, new ListCommand()); + addCommand(commands, new UnbanCommand()); + commands.complete(); + } catch (Exception e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could not register slash commands", e); + } }); }