From 3f6135f427dd9a5d6214dcaa4b8e6d21df56f503 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 20 Mar 2021 14:35:15 +0100 Subject: [PATCH] Revert "Convert some classes to Scala" This reverts commit 261725dc0f65dfd40ae5d1bb26f117defa2b41a6. --- pom.xml | 53 ---- .../discordplugin/ChromaBot.java | 52 ++++ .../discordplugin/DiscordPlugin.java | 292 ++++++++++++++++++ .../discordplugin/DiscordPlugin.scala | 257 --------------- .../listeners/CommandListener.java | 99 ++++++ .../listeners/CommandListener.scala | 105 ------- .../discordplugin/mcchat/MCListener.java | 6 +- src/main/scala/Test.scala | 5 - .../discordplugin/ChromaBot.scala | 36 --- 9 files changed, 446 insertions(+), 459 deletions(-) create mode 100755 src/main/java/buttondevteam/discordplugin/ChromaBot.java create mode 100755 src/main/java/buttondevteam/discordplugin/DiscordPlugin.java delete mode 100644 src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala create mode 100644 src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java delete mode 100644 src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala delete mode 100644 src/main/scala/Test.scala delete mode 100644 src/main/scala/buttondevteam/discordplugin/ChromaBot.scala diff --git a/pom.xml b/pom.xml index 38ff5f4..6d69e8d 100644 --- a/pom.xml +++ b/pom.xml @@ -78,40 +78,6 @@ - - maven-compiler-plugin - - - pre-scala - - -proc:only - - generate-sources - - compile - - - - - - net.alchim31.maven - scala-maven-plugin - 4.4.0 - - - generate-sources - - compile - - - - - - target/generated-sourcess - src/main/java - src/main/scala - - @@ -157,15 +123,6 @@ papermc https://papermc.io/repo/repository/maven-public/ - - - true - - ossSonatypeSnapshot - OSS Sonatype Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - default - @@ -265,15 +222,5 @@ mockito-core 3.5.13 - - org.scala-lang - scala-library - 2.13.1 - - - io.projectreactor - reactor-scala-extensions_2.13 - 0.7.0 - diff --git a/src/main/java/buttondevteam/discordplugin/ChromaBot.java b/src/main/java/buttondevteam/discordplugin/ChromaBot.java new file mode 100755 index 0000000..92f0e9d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/ChromaBot.java @@ -0,0 +1,52 @@ +package buttondevteam.discordplugin; + +import buttondevteam.discordplugin.mcchat.MCChatUtils; +import discord4j.core.object.entity.Message; +import discord4j.core.object.entity.channel.MessageChannel; +import lombok.Getter; +import org.bukkit.scheduler.BukkitScheduler; +import reactor.core.publisher.Mono; + +import javax.annotation.Nullable; +import java.util.function.Function; + +public class ChromaBot { + /** + * May be null if it's not initialized. Initialization happens after the server is done loading (using {@link BukkitScheduler#runTaskAsynchronously(org.bukkit.plugin.Plugin, Runnable)}) + */ + private static @Getter ChromaBot instance; + + /** + * This will set the instance field. + */ + ChromaBot() { + instance = this; + } + + static void delete() { + instance = null; + } + + /** + * Send a message to the chat channels and private chats. + * + * @param message The message to send, duh (use {@link MessageChannel#createMessage(String)}) + */ + public void sendMessage(Function, Mono> message) { + MCChatUtils.forPublicPrivateChat(message::apply).subscribe(); + } + + /** + * Send a message to the chat channels, private chats and custom chats. + * + * @param message The message to send, duh + * @param toggle The toggle type for channelcon + */ + public void sendMessageCustomAsWell(Function, Mono> message, @Nullable ChannelconBroadcast toggle) { + MCChatUtils.forCustomAndAllMCChat(message::apply, toggle, false).subscribe(); + } + + public void updatePlayerList() { + MCChatUtils.updatePlayerList(); + } +} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java new file mode 100755 index 0000000..74022e6 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -0,0 +1,292 @@ +package buttondevteam.discordplugin; + +import buttondevteam.discordplugin.announcer.AnnouncerModule; +import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; +import buttondevteam.discordplugin.commands.*; +import buttondevteam.discordplugin.exceptions.ExceptionListenerModule; +import buttondevteam.discordplugin.fun.FunModule; +import buttondevteam.discordplugin.listeners.CommonListeners; +import buttondevteam.discordplugin.listeners.MCListener; +import buttondevteam.discordplugin.mcchat.MinecraftChatModule; +import buttondevteam.discordplugin.mccommands.DiscordMCCommand; +import buttondevteam.discordplugin.role.GameRoleModule; +import buttondevteam.discordplugin.util.DPState; +import buttondevteam.discordplugin.util.Timings; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.architecture.ButtonPlugin; +import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.architecture.ConfigData; +import buttondevteam.lib.architecture.IHaveConfig; +import buttondevteam.lib.player.ChromaGamerBase; +import com.google.common.io.Files; +import discord4j.common.util.Snowflake; +import discord4j.core.DiscordClientBuilder; +import discord4j.core.GatewayDiscordClient; +import discord4j.core.event.domain.guild.GuildCreateEvent; +import discord4j.core.event.domain.lifecycle.ReadyEvent; +import discord4j.core.object.entity.Guild; +import discord4j.core.object.entity.Role; +import discord4j.core.object.presence.Activity; +import discord4j.core.object.presence.Presence; +import discord4j.core.object.reaction.ReactionEmoji; +import discord4j.store.jdk.JdkStoreService; +import lombok.Getter; +import lombok.val; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; +import org.bukkit.configuration.file.YamlConfiguration; +import org.mockito.internal.util.MockUtil; +import reactor.core.publisher.Mono; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +@ButtonPlugin.ConfigOpts(disableConfigGen = true) +public class DiscordPlugin extends ButtonPlugin { + public static GatewayDiscordClient dc; + public static DiscordPlugin plugin; + public static boolean SafeMode = true; + @Getter + private Command2DC manager; + private boolean starting; + private BukkitLogWatcher logWatcher; + + /** + * The prefix to use with Discord commands like /role. It only works in the bot channel. + */ + private final ConfigData prefix = getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString); + + public static char getPrefix() { + if (plugin == null) return '/'; + return plugin.prefix.get(); + } + + /** + * The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to. + */ + private ConfigData> mainServer() { + return getIConfig().getDataPrimDef("mainServer", 0L, + id -> { + //It attempts to get the default as well + if ((long) id == 0L) + return Optional.empty(); //Hack? + return dc.getGuildById(Snowflake.of((long) id)) + .onErrorResume(t -> Mono.fromRunnable(() -> getLogger().warning("Failed to get guild: " + t.getMessage()))).blockOptional(); + }, + g -> g.map(gg -> gg.getId().asLong()).orElse(0L)); + } + + /** + * The (bot) channel to use for Discord commands like /role. + */ + public ConfigData commandChannel = DPUtils.snowflakeData(getIConfig(), "commandChannel", 0L); + + /** + * The role that allows using mod-only Discord commands. + * If empty (''), then it will only allow for the owner. + */ + public ConfigData> modRole; + + /** + * The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access. + */ + public ConfigData inviteLink = getIConfig().getData("inviteLink", ""); + + private void setupConfig() { + modRole = DPUtils.roleData(getIConfig(), "modRole", "Moderator"); + } + + @Override + public void onLoad() { //Needed by ServerWatcher + var thread = Thread.currentThread(); + var cl = thread.getContextClassLoader(); + thread.setContextClassLoader(getClassLoader()); + MockUtil.isMock(null); //Load MockUtil to load Mockito plugins + thread.setContextClassLoader(cl); + getLogger().info("Load complete"); + } + + @Override + public void pluginEnable() { + try { + getLogger().info("Initializing..."); + plugin = this; + manager = new Command2DC(); + registerCommand(new DiscordMCCommand()); //Register so that the restart command works + String token; + File tokenFile = new File("TBMC", "Token.txt"); + if (tokenFile.exists()) //Legacy support + //noinspection UnstableApiUsage + token = Files.readFirstLine(tokenFile, StandardCharsets.UTF_8); + else { + File privateFile = new File(getDataFolder(), "private.yml"); + val conf = YamlConfiguration.loadConfiguration(privateFile); + token = conf.getString("token"); + if (token == null || token.equalsIgnoreCase("Token goes here")) { + conf.set("token", "Token goes here"); + conf.save(privateFile); + + getLogger().severe("Token not found! Please set it in private.yml then do /discord restart"); + getLogger().severe("You need to have a bot account to use with your server."); + getLogger().severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token."); + return; + } + } + starting = true; + //System.out.println("This line should show up for sure"); + val cb = DiscordClientBuilder.create(token).build().gateway(); + //System.out.println("Got gateway bootstrap"); + cb.setInitialStatus(si -> Presence.doNotDisturb(Activity.playing("booting"))); + cb.setStoreService(new JdkStoreService()); //The default doesn't work for some reason - it's waaay faster now + //System.out.println("Initial status and store service set"); + cb.login().doOnError(t -> { + stopStarting(); + //System.out.println("Got this error: " + t); t.printStackTrace(); + }).subscribe(dc -> { + //System.out.println("Login successful, got dc: " + dc); + DiscordPlugin.dc = dc; //Set to gateway client + dc.on(ReadyEvent.class) // Listen for ReadyEvent(s) + .map(event -> event.getGuilds().size()) // Get how many guilds the bot is in + .flatMap(size -> dc + .on(GuildCreateEvent.class) // Listen for GuildCreateEvent(s) + .take(size) // Take only the first `size` GuildCreateEvent(s) to be received + .doOnError(t -> { + stopStarting(); + //System.out.println("Got error: " + t); + t.printStackTrace(); + }) + .collectList()).doOnError(t -> stopStarting()).subscribe(this::handleReady); // Take all received GuildCreateEvents and make it a List + }); /* All guilds have been received, client is fully connected */ + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this); + getLogger().severe("You may be able to restart the plugin using /discord restart"); + stopStarting(); + } + } + + private void stopStarting() { + synchronized (this) { + starting = false; + notifyAll(); + } + } + + public static Guild mainServer; + + private void handleReady(List event) { + //System.out.println("Got ready event"); + try { + if (mainServer != null) { //This is not the first ready event + getLogger().info("Ready event already handled"); //TODO: It should probably handle disconnections + dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe(); //Update from the initial presence + return; + } + mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards + if (mainServer == null) { + if (event.size() == 0) { + getLogger().severe("Main server not found! Invite the bot and do /discord restart"); + dc.getApplicationInfo().subscribe(info -> + getLogger().severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId().asString() + "&scope=bot&permissions=268509264")); + saveConfig(); //Put default there + return; //We should have all guilds by now, no need to retry + } + mainServer = event.get(0).getGuild(); + getLogger().warning("Main server set to first one: " + mainServer.getName()); + mainServer().set(Optional.of(mainServer)); //Save in config + } + SafeMode = false; + setupConfig(); + DPUtils.disableIfConfigErrorRes(null, commandChannel, DPUtils.getMessageChannel(commandChannel)); + //Won't disable, just prints the warning here + + if (MinecraftChatModule.state == DPState.STOPPING_SERVER) { + stopStarting(); + return; //Reusing that field to check if stopping while still initializing + } + CommonListeners.register(dc.getEventDispatcher()); + TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); + TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class, DiscordPlayer::new); + ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase + ? ((DiscordSenderBase) sender).getChromaUser() : null)); + + IHaveConfig.pregenConfig(this, null); + + var cb = new ChromaBot(); //Initialize ChromaBot + Component.registerComponent(this, new GeneralEventBroadcasterModule()); + Component.registerComponent(this, new MinecraftChatModule()); + Component.registerComponent(this, new ExceptionListenerModule()); + Component.registerComponent(this, new GameRoleModule()); //Needs the mainServer to be set + Component.registerComponent(this, new AnnouncerModule()); + Component.registerComponent(this, new FunModule()); + cb.updatePlayerList(); //The MCChatModule is tested to be enabled + + getManager().registerCommand(new VersionCommand()); + getManager().registerCommand(new UserinfoCommand()); + getManager().registerCommand(new HelpCommand()); + getManager().registerCommand(new DebugCommand()); + getManager().registerCommand(new ConnectCommand()); + + TBMCCoreAPI.SendUnsentExceptions(); + TBMCCoreAPI.SendUnsentDebugMessages(); + + var blw = new BukkitLogWatcher(); + blw.start(); + ((Logger) LogManager.getRootLogger()).addAppender(blw); + logWatcher = blw; + + if (!TBMCCoreAPI.IsTestServer()) { + dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe(); + } else { + dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe(); + } + getLogger().info("Loaded!"); + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occurred while enabling DiscordPlugin!", e, this); + } + stopStarting(); + } + + @Override + public void pluginPreDisable() { + if (MinecraftChatModule.state == DPState.RUNNING) + MinecraftChatModule.state = DPState.STOPPING_SERVER; + synchronized (this) { + if (starting) { + try { + wait(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + if (ChromaBot.getInstance() == null) return; //Failed to load + Timings timings = new Timings(); + timings.printElapsed("Disable start"); + timings.printElapsed("Updating player list"); + ChromaBot.getInstance().updatePlayerList(); + timings.printElapsed("Done"); + } + + @Override + public void pluginDisable() { + Timings timings = new Timings(); + timings.printElapsed("Actual disable start (logout)"); + if (ChromaBot.getInstance() == null) return; //Failed to load + + try { + SafeMode = true; // Stop interacting with Discord + ChromaBot.delete(); + ((Logger) LogManager.getRootLogger()).removeAppender(logWatcher); + timings.printElapsed("Logging out..."); + dc.logout().block(); + mainServer = null; //Allow ReadyEvent again + //Configs are emptied so channels and servers are fetched again + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e, this); + } + } + + public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.unicode("✅"); +} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala deleted file mode 100644 index 7624ec8..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala +++ /dev/null @@ -1,257 +0,0 @@ -package buttondevteam.discordplugin - -import buttondevteam.discordplugin.announcer.AnnouncerModule -import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule -import buttondevteam.discordplugin.commands._ -import buttondevteam.discordplugin.exceptions.ExceptionListenerModule -import buttondevteam.discordplugin.fun.FunModule -import buttondevteam.discordplugin.listeners.{CommonListeners, MCListener} -import buttondevteam.discordplugin.mcchat.MinecraftChatModule -import buttondevteam.discordplugin.mccommands.DiscordMCCommand -import buttondevteam.discordplugin.role.GameRoleModule -import buttondevteam.discordplugin.util.{DPState, Timings} -import buttondevteam.lib.TBMCCoreAPI -import buttondevteam.lib.architecture._ -import buttondevteam.lib.player.ChromaGamerBase -import com.google.common.io.Files -import discord4j.common.util.Snowflake -import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient} -import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role} -import discord4j.core.`object`.presence.{Activity, Presence} -import discord4j.core.`object`.reaction.ReactionEmoji -import discord4j.core.event.domain.guild.GuildCreateEvent -import discord4j.core.event.domain.lifecycle.ReadyEvent -import discord4j.gateway.ShardInfo -import discord4j.store.jdk.JdkStoreService -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.core.Logger -import org.bukkit.command.CommandSender -import org.bukkit.configuration.file.YamlConfiguration -import org.mockito.internal.util.MockUtil -import reactor.core.Disposable -import reactor.core.publisher.Mono - -import java.io.File -import java.nio.charset.StandardCharsets -import java.util.Optional - -@ButtonPlugin.ConfigOpts(disableConfigGen = true) object DiscordPlugin { - private[discordplugin] var dc: GatewayDiscordClient = null - private[discordplugin] var plugin: DiscordPlugin = null - private[discordplugin] var SafeMode = true - - def getPrefix: Char = { - if (plugin == null) return '/' - plugin.prefix.get - } - - private[discordplugin] var mainServer: Guild = null - private[discordplugin] val DELIVERED_REACTION = ReactionEmoji.unicode("✅") -} - -@ButtonPlugin.ConfigOpts(disableConfigGen = true) class DiscordPlugin extends ButtonPlugin { - private var _manager: Command2DC = null - - def manager: Command2DC = _manager - - private var starting = false - private var logWatcher: BukkitLogWatcher = null - /** - * The prefix to use with Discord commands like /role. It only works in the bot channel. - */ - final private val prefix = getIConfig.getData("prefix", '/', (str: Any) => str.asInstanceOf[String].charAt(0), (_: Char).toString) - - /** - * The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to. - */ - private def mainServer = getIConfig.getDataPrimDef("mainServer", 0L, (id: Any) => { - def foo(id: Any) = { //It attempts to get the default as well - if (id.asInstanceOf[Long] == 0L) Optional.empty //Hack? - else DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long])).onErrorResume((t: Throwable) => Mono.fromRunnable(() => getLogger.warning("Failed to get guild: " + t.getMessage))).blockOptional - } - - foo(id) - }, (g: Optional[Guild]) => (g.map((gg: Guild) => gg.getId.asLong): Optional[Long]).orElse(0L)) - - /** - * The (bot) channel to use for Discord commands like /role. - */ - var commandChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getIConfig, "commandChannel", 0L) - /** - * The role that allows using mod-only Discord commands. - * If empty (''), then it will only allow for the owner. - */ - var modRole: ReadOnlyConfigData[Mono[Role]] = null - /** - * The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access. - */ - var inviteLink: ConfigData[String] = getIConfig.getData("inviteLink", "") - - private def setupConfig(): Unit = modRole = DPUtils.roleData(getIConfig, "modRole", "Moderator") - - override def onLoad(): Unit = { //Needed by ServerWatcher - val thread = Thread.currentThread - val cl = thread.getContextClassLoader - thread.setContextClassLoader(getClassLoader) - MockUtil.isMock(null) //Load MockUtil to load Mockito plugins - thread.setContextClassLoader(cl) - getLogger.info("Load complete") - } - - override def pluginEnable(): Unit = try { - getLogger.info("Initializing...") - DiscordPlugin.plugin = this - _manager = new Command2DC - registerCommand(new DiscordMCCommand) //Register so that the restart command works - var token: String = null - val tokenFile = new File("TBMC", "Token.txt") - if (tokenFile.exists) { //Legacy support - //noinspection UnstableApiUsage - token = Files.readFirstLine(tokenFile, StandardCharsets.UTF_8) - } - else { - val privateFile = new File(getDataFolder, "private.yml") - val conf = YamlConfiguration.loadConfiguration(privateFile) - token = conf.getString("token") - if (token == null || token.equalsIgnoreCase("Token goes here")) { - conf.set("token", "Token goes here") - conf.save(privateFile) - getLogger.severe("Token not found! Please set it in private.yml then do /discord restart") - getLogger.severe("You need to have a bot account to use with your server.") - getLogger.severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token.") - return - } - } - starting = true - //System.out.println("This line should show up for sure"); - val cb = DiscordClientBuilder.create(token).build.gateway - //System.out.println("Got gateway bootstrap"); - cb.setInitialStatus((si: ShardInfo) => Presence.doNotDisturb(Activity.playing("booting"))) - cb.setStoreService(new JdkStoreService) //The default doesn't work for some reason - it's waaay faster now - //System.out.println("Initial status and store service set"); - cb.login.doOnError((t: Throwable) => { - def foo(t: Throwable): Unit = { - stopStarting() - //System.out.println("Got this error: " + t); t.printStackTrace(); - } - - foo(t) - }).subscribe((dc: GatewayDiscordClient) => { - def foo(dc: GatewayDiscordClient): Disposable = { //System.out.println("Login successful, got dc: " + dc); - DiscordPlugin.dc = dc //Set to gateway client - dc.on(classOf[ReadyEvent]).map(_.getGuilds.size).flatMap(dc.on(classOf[GuildCreateEvent]).take(_).collectList) - .doOnError(_ => stopStarting()).subscribe(this.handleReady _) // Take all received GuildCreateEvents and make it a List - } - - foo(dc) - }) /* All guilds have been received, client is fully connected */ - } catch { - case e: Exception => - TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this) - getLogger.severe("You may be able to restart the plugin using /discord restart") - stopStarting() - } - - private def stopStarting(): Unit = { - this synchronized (starting = false) - notifyAll() - } - - private def handleReady(event: java.util.List[GuildCreateEvent]): Unit = { //System.out.println("Got ready event"); - try { - if (DiscordPlugin.mainServer != null) { //This is not the first ready event - getLogger.info("Ready event already handled") //TODO: It should probably handle disconnections - DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence - return - } - DiscordPlugin.mainServer = mainServer.get.orElse(null) //Shouldn't change afterwards - if (DiscordPlugin.mainServer == null) { - if (event.size == 0) { - getLogger.severe("Main server not found! Invite the bot and do /discord restart") - DiscordPlugin.dc.getApplicationInfo.subscribe((info: ApplicationInfo) => getLogger.severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId.asString + "&scope=bot&permissions=268509264")) - saveConfig() //Put default there - return //We should have all guilds by now, no need to retry - } - DiscordPlugin.mainServer = event.get(0).getGuild - getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName) - mainServer.set(Optional.of(DiscordPlugin.mainServer)) //Save in config - } - DiscordPlugin.SafeMode = false - setupConfig() - DPUtils.disableIfConfigErrorRes(null, commandChannel, DPUtils.getMessageChannel(commandChannel)) - //Won't disable, just prints the warning here - if (MinecraftChatModule.state eq DPState.STOPPING_SERVER) { - stopStarting() - return //Reusing that field to check if stopping while still initializing - } - CommonListeners.register(DiscordPlugin.dc.getEventDispatcher) - TBMCCoreAPI.RegisterEventsForExceptions(new MCListener, this) - TBMCCoreAPI.RegisterUserClass(classOf[DiscordPlayer], () => new DiscordPlayer) - ChromaGamerBase.addConverter((sender: CommandSender) => Optional.ofNullable(sender match { - case dsender: DiscordSenderBase => dsender.getChromaUser - case _ => null - })) - IHaveConfig.pregenConfig(this, null) - ChromaBot.enabled = true //Initialize ChromaBot - Component.registerComponent(this, new GeneralEventBroadcasterModule) - Component.registerComponent(this, new MinecraftChatModule) - Component.registerComponent(this, new ExceptionListenerModule) - Component.registerComponent(this, new GameRoleModule) //Needs the mainServer to be set - Component.registerComponent(this, new AnnouncerModule) - Component.registerComponent(this, new FunModule) - ChromaBot.updatePlayerList() //The MCChatModule is tested to be enabled - manager.registerCommand(new VersionCommand) - manager.registerCommand(new UserinfoCommand) - manager.registerCommand(new HelpCommand) - manager.registerCommand(new DebugCommand) - manager.registerCommand(new ConnectCommand) - TBMCCoreAPI.SendUnsentExceptions() - TBMCCoreAPI.SendUnsentDebugMessages() - val blw = new BukkitLogWatcher - blw.start() - LogManager.getRootLogger.asInstanceOf[Logger].addAppender(blw) - logWatcher = blw - if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe - else DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe - getLogger.info("Loaded!") - } catch { - case e: Exception => - TBMCCoreAPI.SendException("An error occurred while enabling DiscordPlugin!", e, this) - } - stopStarting() - } - - override def pluginPreDisable(): Unit = { - if (MinecraftChatModule.state eq DPState.RUNNING) MinecraftChatModule.state = DPState.STOPPING_SERVER - this synchronized { - if (starting) try wait(10000) - catch { - case e: InterruptedException => - e.printStackTrace() - } - } - if (!ChromaBot.enabled) return //Failed to load - val timings = new Timings - timings.printElapsed("Disable start") - timings.printElapsed("Updating player list") - ChromaBot.updatePlayerList() - timings.printElapsed("Done") - } - - override def pluginDisable(): Unit = { - val timings = new Timings - timings.printElapsed("Actual disable start (logout)") - if (!ChromaBot.enabled) return - try { - DiscordPlugin.SafeMode = true // Stop interacting with Discord - ChromaBot.enabled = false - LogManager.getRootLogger.asInstanceOf[Logger].removeAppender(logWatcher) - timings.printElapsed("Logging out...") - DiscordPlugin.dc.logout.block - DiscordPlugin.mainServer = null //Allow ReadyEvent again - } catch { - case e: Exception => - TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e, this) - } - } -} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java new file mode 100644 index 0000000..08bd40f --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java @@ -0,0 +1,99 @@ +package buttondevteam.discordplugin.listeners; + +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.discordplugin.commands.Command2DCSender; +import buttondevteam.discordplugin.util.Timings; +import buttondevteam.lib.TBMCCoreAPI; +import discord4j.common.util.Snowflake; +import discord4j.core.object.entity.Message; +import discord4j.core.object.entity.Role; +import discord4j.core.object.entity.channel.PrivateChannel; +import lombok.val; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class CommandListener { + /** + * Runs a ChromaBot command. If mentionedonly is false, it will only execute the command if it was in #bot with the correct prefix or in private. + * + * @param message The Discord message + * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message + * @return Whether it did not run the command + */ + public static Mono runCommand(Message message, Snowflake commandChannelID, boolean mentionedonly) { + Timings timings = CommonListeners.timings; + Mono ret = Mono.just(true); + if (message.getContent().length() == 0) + return ret; //Pin messages and such, let the mcchat listener deal with it + val content = message.getContent(); + timings.printElapsed("A"); + return message.getChannel().flatMap(channel -> { + Mono tmp = ret; + if (!mentionedonly) { //mentionedonly conditions are in CommonListeners + timings.printElapsed("B"); + if (!(channel instanceof PrivateChannel) + && !(content.charAt(0) == DiscordPlugin.getPrefix() + && channel.getId().asLong() == commandChannelID.asLong())) // + return ret; + timings.printElapsed("C"); + tmp = ret.then(channel.type()).thenReturn(true); // Fun (this true is ignored - x) + } + final StringBuilder cmdwithargs = new StringBuilder(content); + val gotmention = new AtomicBoolean(); + timings.printElapsed("Before self"); + return tmp.flatMapMany(x -> + DiscordPlugin.dc.getSelf().flatMap(self -> self.asMember(DiscordPlugin.mainServer.getId())) + .flatMapMany(self -> { + timings.printElapsed("D"); + gotmention.set(checkanddeletemention(cmdwithargs, self.getMention(), message)); + gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention(), message) || gotmention.get()); + val mentions = message.getRoleMentions(); + return self.getRoles().filterWhen(r -> mentions.any(rr -> rr.getName().equals(r.getName()))) + .map(Role::getMention); + }).map(mentionRole -> { + timings.printElapsed("E"); + gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get()); // Delete all mentions + return !mentionedonly || gotmention.get(); //Stops here if false + }).switchIfEmpty(Mono.fromSupplier(() -> !mentionedonly || gotmention.get()))) + .filter(b -> b).last(false).filter(b -> b).doOnNext(b -> channel.type().subscribe()).flatMap(b -> { + String cmdwithargsString = cmdwithargs.toString(); + try { + timings.printElapsed("F"); + if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString)) + return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.") + .map(m -> false); + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin); + } + return Mono.just(false); //If the command succeeded or there was an error, return false + }).defaultIfEmpty(true); + }); + } + + private static boolean checkanddeletemention(StringBuilder cmdwithargs, String mention, Message message) { + final char prefix = DiscordPlugin.getPrefix(); + if (message.getContent().startsWith(mention)) // TODO: Resolve mentions: Compound arguments, either a mention or text + if (cmdwithargs.length() > mention.length() + 1) { + int i = cmdwithargs.indexOf(" ", mention.length()); + if (i == -1) + i = mention.length(); + else + //noinspection StatementWithEmptyBody + for (; i < cmdwithargs.length() && cmdwithargs.charAt(i) == ' '; i++) + ; //Removes any space before the command + cmdwithargs.delete(0, i); + cmdwithargs.insert(0, prefix); //Always use the prefix for processing + } else + cmdwithargs.replace(0, cmdwithargs.length(), prefix + "help"); + else { + if (cmdwithargs.length() == 0) + cmdwithargs.replace(0, 0, prefix + "help"); + else if (cmdwithargs.charAt(0) != prefix) + cmdwithargs.insert(0, prefix); + return false; //Don't treat / as mention, mentions can be used in public mcchat + } + return true; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala deleted file mode 100644 index a810307..0000000 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala +++ /dev/null @@ -1,105 +0,0 @@ -package buttondevteam.discordplugin.listeners - -import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} -import buttondevteam.discordplugin.commands.Command2DCSender -import buttondevteam.lib.TBMCCoreAPI -import discord4j.common.util.Snowflake -import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} -import discord4j.core.`object`.entity.{Member, Message, Role, User} -import reactor.core.publisher.Mono - -import java.util.concurrent.atomic.AtomicBoolean - -object CommandListener { - /** - * Runs a ChromaBot command. If mentionedonly is false, it will only execute the command if it was in #bot with the correct prefix or in private. - * - * @param message The Discord message - * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message - * @return Whether it did not run the command - */ - def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): Mono[Boolean] = { - val timings = CommonListeners.timings - val ret = Mono.just(true) - if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it - val content = message.getContent - timings.printElapsed("A") - message.getChannel.flatMap((channel: MessageChannel) => { - def foo(channel: MessageChannel): Mono[Boolean] = { - var tmp = ret - if (!mentionedonly) { //mentionedonly conditions are in CommonListeners - timings.printElapsed("B") - if (!channel.isInstanceOf[PrivateChannel] && !(content.charAt(0) == DiscordPlugin.getPrefix && channel.getId.asLong == commandChannelID.asLong)) { // - return ret - } - timings.printElapsed("C") - tmp = ret.`then`(channel.`type`).thenReturn(true) // Fun (this true is ignored - x) - } - val cmdwithargs = new StringBuilder(content) - val gotmention = new AtomicBoolean - timings.printElapsed("Before self") - tmp.flatMapMany((x: Boolean) => DiscordPlugin.dc.getSelf.flatMap((self: User) => self.asMember(DiscordPlugin.mainServer.getId)).flatMapMany((self: Member) => { - def foo(self: Member) = { - timings.printElapsed("D") - gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) - gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) - val mentions = message.getRoleMentions - self.getRoles.filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) - } - - foo(self) - }).map((mentionRole: String) => { - def foo(mentionRole: String) = { - timings.printElapsed("E") - gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions - !mentionedonly || gotmention.get //Stops here if false - } - - foo(mentionRole) - }).switchIfEmpty(Mono.fromSupplier(() => !mentionedonly || gotmention.get))).filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => { - def foo(): Mono[Boolean] = { - val cmdwithargsString = cmdwithargs.toString - try { - timings.printElapsed("F") - if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map((_: Message) => false) - } catch { - case e: Exception => - TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin) - } - Mono.just(false) //If the command succeeded or there was an error, return false - } - - foo() - }).defaultIfEmpty(true) - } - - foo(channel) - }) - } - - private def checkanddeletemention(cmdwithargs: StringBuilder, mention: String, message: Message): Boolean = { - val prefix = DiscordPlugin.getPrefix - if (message.getContent.startsWith(mention)) { // TODO: Resolve mentions: Compound arguments, either a mention or text - if (cmdwithargs.length > mention.length + 1) { - var i = cmdwithargs.indexOf(" ", mention.length) - if (i == -1) i = mention.length - else { //noinspection StatementWithEmptyBody - while ( { - i < cmdwithargs.length && cmdwithargs.charAt(i) == ' ' - }) { //Removes any space before the command - i += 1 - } - } - cmdwithargs.delete(0, i) - cmdwithargs.insert(0, prefix) //Always use the prefix for processing - } - else cmdwithargs.replace(0, cmdwithargs.length, prefix + "help") - } - else { - if (cmdwithargs.isEmpty) cmdwithargs.replace(0, 0, prefix + "help") - else if (cmdwithargs.charAt(0) != prefix) cmdwithargs.insert(0, prefix) - return false //Don't treat / as mention, mentions can be used in public mcchat - } - true - } -} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index f76c5a9..19cc441 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -68,7 +68,7 @@ class MCListener implements Listener { final String message = e.getJoinMessage(); if (message != null && message.trim().length() > 0) MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe(); - ChromaBot.updatePlayerList(); + ChromaBot.getInstance().updatePlayerList(); }); } @@ -81,7 +81,7 @@ class MCListener implements Listener { Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents)); Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, - ChromaBot::updatePlayerList, 5); + ChromaBot.getInstance()::updatePlayerList, 5); final String message = e.getQuitMessage(); if (message != null && message.trim().length() > 0) MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe(); @@ -119,7 +119,7 @@ class MCListener implements Listener { final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class) .getAs(DiscordPlayer.class); if (p == null) return; - DPUtils.ignoreError(DiscordPlugin.dc().getUserById(Snowflake.of(p.getDiscordID())) + DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID())) .flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId())) .flatMap(user -> role.flatMap(r -> { if (e.getValue()) diff --git a/src/main/scala/Test.scala b/src/main/scala/Test.scala deleted file mode 100644 index 743d12a..0000000 --- a/src/main/scala/Test.scala +++ /dev/null @@ -1,5 +0,0 @@ -import buttondevteam.discordplugin.DiscordPlugin - -object Test extends App { - println(DiscordPlugin.plugin) -} \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala b/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala deleted file mode 100644 index 6a8b7e0..0000000 --- a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala +++ /dev/null @@ -1,36 +0,0 @@ -package buttondevteam.discordplugin - -import buttondevteam.discordplugin.mcchat.MCChatUtils -import discord4j.core.`object`.entity.Message -import discord4j.core.`object`.entity.channel.MessageChannel -import reactor.core.publisher.Mono - -import javax.annotation.Nullable - -object ChromaBot { - private var _enabled = false - - def enabled = _enabled - - private[discordplugin] def enabled_=(en: Boolean): Unit = _enabled = en - - /** - * Send a message to the chat channels and private chats. - * - * @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )}) - */ - def sendMessage(message: java.util.function.Function[Mono[MessageChannel], Mono[Message]]): Unit = - MCChatUtils.forPublicPrivateChat(message.apply(_)).subscribe - - /** - * Send a message to the chat channels, private chats and custom chats. - * - * @param message The message to send, duh - * @param toggle The toggle type for channelcon - */ - def sendMessageCustomAsWell(message: Function[Mono[MessageChannel], Mono[Message]], @Nullable toggle: ChannelconBroadcast): Unit = - MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, false).subscribe - - def updatePlayerList(): Unit = - MCChatUtils.updatePlayerList() -}