diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index 8e03848..9521482 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -1,5 +1,6 @@ package buttondevteam.discordplugin +import buttondevteam.discordplugin.DiscordPlugin.dc import buttondevteam.discordplugin.announcer.AnnouncerModule import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule import buttondevteam.discordplugin.commands.* @@ -84,7 +85,7 @@ import java.util.Optional 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. + * If empty (''), then it will only allow for the owner. */ var modRole: ReadOnlyConfigData[SMono[Role]] = null /** @@ -204,19 +205,19 @@ import java.util.Optional 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) + val applicationId = dc.getRestClient.getApplicationId.block() + val guildId = Some(DiscordPlugin.mainServer.getId.asLong()) + manager.registerCommand(new VersionCommand, applicationId, guildId) + manager.registerCommand(new UserinfoCommand, applicationId, guildId) + manager.registerCommand(new HelpCommand, applicationId, guildId) + manager.registerCommand(new DebugCommand, applicationId, guildId) + manager.registerCommand(new ConnectCommand, applicationId, guildId) TBMCCoreAPI.SendUnsentExceptions() TBMCCoreAPI.SendUnsentDebugMessages() val blw = new BukkitLogWatcher blw.start() LogManager.getRootLogger.asInstanceOf[Logger].addAppender(blw) logWatcher = blw - Interactions.create().onCommand("teszt", Interactions.createHandler() - .guild(gi => gi.acknowledge().withFollowup(_.createFollowupMessage("Teszt"))).build()); if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("Minecraft"))).subscribe() else DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("testing"))).subscribe() getLogger.info("Loaded!") diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala index 9d35368..46a9e69 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -46,11 +46,11 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC val sendmsg = DPUtils.sanitizeString(message) this synchronized { msgtosend += "\n" + sendmsg - if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { + if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, (() => { channel.createMessage((if (user != null) user.getMention + "\n" else "") + msgtosend.trim).subscribe() sendtask = null msgtosend = "" - }, 4) // Waits a 0.2 second to gather all/most of the different messages + }): Runnable, 4) // Waits a 0.2 second to gather all/most of the different messages } } catch { case e: Exception => diff --git a/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala b/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala index 690cb2d..7301784 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala @@ -2,12 +2,36 @@ package buttondevteam.discordplugin.commands import buttondevteam.discordplugin.DiscordPlugin import buttondevteam.lib.chat.Command2 +import discord4j.common.util.Snowflake +import discord4j.core.`object`.command.ApplicationCommandOption +import discord4j.discordjson.json.{ApplicationCommandOptionData, ApplicationCommandRequest} import java.lang.reflect.Method class Command2DC extends Command2[ICommand2DC, Command2DCSender] { - override def registerCommand(command: ICommand2DC): Unit = + override def registerCommand(command: ICommand2DC): Unit = { + registerCommand(command, DiscordPlugin.dc.getApplicationInfo.block().getId.asLong()) + } + + def registerCommand(command: ICommand2DC, appId: Long, guildId: Option[Long] = None): Unit = { super.registerCommand(command, DiscordPlugin.getPrefix) //Needs to be configurable for the helps + val greetCmdRequest = ApplicationCommandRequest.builder() + .name(command.getCommandPath) //TODO: Main path + .description("A ChromaBot command.") //TODO: Description + .addOption(ApplicationCommandOptionData.builder() + .name("name") + .description("Your name") + .`type`(ApplicationCommandOption.Type.STRING.getValue) + .required(true) + .build() + ).build() + val service = DiscordPlugin.dc.getRestClient.getApplicationService + guildId match { + case Some(id) => service.createGuildApplicationCommand(appId, id, greetCmdRequest).subscribe() + case None => service.createGlobalApplicationCommand(appId, greetCmdRequest).subscribe() + } + } + override def hasPermission(sender: Command2DCSender, command: ICommand2DC, method: Method): Boolean = { //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.modRole().get()); //TODO: modRole may be null; more customisable way? true diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala deleted file mode 100644 index 0ae0459..0000000 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala +++ /dev/null @@ -1,110 +0,0 @@ -package buttondevteam.discordplugin.listeners - -import buttondevteam.discordplugin.commands.Command2DCSender -import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} -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.scala.publisher.{SFlux, SMono} - -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): SMono[Boolean] = { - val timings = CommonListeners.timings - val ret = SMono.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") - SMono(message.getChannel).flatMap((channel: MessageChannel) => { - def foo(channel: MessageChannel): SMono[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`(SMono(channel.`type`)).`then`(ret) // Fun (this true is ignored - x) - } - val cmdwithargs = new StringBuilder(content) - val gotmention = new AtomicBoolean - timings.printElapsed("Before self") - tmp.flatMapMany((_: Boolean) => SMono(DiscordPlugin.dc.getSelf) - .flatMap((self: User) => SMono(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 = SFlux(message.getRoleMentions) - SFlux(self.getRoles).filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) - } - - foo(self) - }).map((mentionRole: String) => { - def foo(mentionRole: String): Boolean = { - timings.printElapsed("E") - gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions - !mentionedonly || gotmention.get //Stops here if false - } - - foo(mentionRole) - }).switchIfEmpty(SMono.fromCallable(() => !mentionedonly || gotmention.get))).filter(b => b) - .last(Option(false)).filter(b => b) - .doOnNext(_ => channel.`type`.subscribe).flatMap(_ => { - def foo(): SMono[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(_ => false) - } catch { - case e: Exception => - TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin) - } - SMono.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/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala index c72be51..3b7da35 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -11,29 +11,26 @@ import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.event.EventDispatcher import discord4j.core.event.domain.PresenceUpdateEvent +import discord4j.core.event.domain.interaction.{ChatInputInteractionEvent, MessageInteractionEvent} import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} import reactor.core.Disposable +import reactor.core.publisher.Mono import reactor.core.scala.publisher.{SFlux, SMono} object CommonListeners { val timings = new Timings - /* - MentionEvent: - - CommandListener (starts with mention, only 'channelcon' and not in #bot) - - MessageReceivedEvent: - - v CommandListener (starts with mention, in #bot or a connected chat) - - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - - CommandListener (with the correct prefix in #bot, or in private) - */ def register(dispatcher: EventDispatcher): Unit = { dispatcher.on(classOf[MessageCreateEvent]).flatMap((event: MessageCreateEvent) => { SMono.just(event.getMessage).filter(_ => !DiscordPlugin.SafeMode) .filter(message => message.getAuthor.filter(!_.isBot).isPresent) .filter(message => !FunModule.executeMemes(message)) - .flatMap(handleMessage(event)) + .filterWhen(message => { + Option(Component.getComponents.get(classOf[MinecraftChatModule])).filter(_.isEnabled) + .map(_.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event)) + .getOrElse(SMono.just(true)) //Wasn't handled, continue + }) }).onErrorContinue((err, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe() dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { if (!DiscordPlugin.SafeMode) @@ -42,6 +39,12 @@ object CommonListeners { SFlux(dispatcher.on(classOf[RoleCreateEvent])).subscribe(GameRoleModule.handleRoleEvent) SFlux(dispatcher.on(classOf[RoleDeleteEvent])).subscribe(GameRoleModule.handleRoleEvent) SFlux(dispatcher.on(classOf[RoleUpdateEvent])).subscribe(GameRoleModule.handleRoleEvent) + SFlux(dispatcher.on(classOf[ChatInputInteractionEvent], event => { + if(event.getCommandName() eq "help") + event.reply("Hello there") + else + Mono.empty() + })).subscribe() } var debug = false @@ -49,20 +52,4 @@ object CommonListeners { def debug(debug: String): Unit = if (CommonListeners.debug) { //Debug DPUtils.getLogger.info(debug) } - - private def handleMessage(event: MessageCreateEvent) = { - (message: Message) => { - val commandChannel = Option(DiscordPlugin.plugin.commandChannel.get) - SMono(message.getChannel).filter(mch => commandChannel.isDefined && mch.getId.asLong() == commandChannel.get.asLong() //If mentioned, that's higher than chat - || mch.isInstanceOf[PrivateChannel] || message.getContent.contains("channelcon")).flatMap(_ => { //Only 'channelcon' is allowed in other channels - //Only continue if this doesn't handle the event - CommandListener.runCommand(message, commandChannel.get, mentionedonly = true) //#bot is handled here - }).`then`(SMono.just(true)) //The condition is only for the first command execution, not mcchat - .filterWhen(_ => { - Option(Component.getComponents.get(classOf[MinecraftChatModule])).filter(_.isEnabled) - .map(_.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event)) //Also runs Discord commands in chat channels - .getOrElse(SMono.just(true)) //Wasn't handled, continue - }).filterWhen(_ => CommandListener.runCommand(event.getMessage, commandChannel.get, mentionedonly = false)) - } - } } \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 18fb427..c1c1af9 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -3,7 +3,6 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager import buttondevteam.discordplugin.* import buttondevteam.discordplugin.DPUtils.SpecExtensions -import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} import buttondevteam.lib.* import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI} @@ -251,7 +250,6 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { SMono(ev.getMessage.getChannel) .filter(channel => isChatEnabled(channel, author, hasCustomChat)) .filter(channel => !isRunningMCChatCommand(channel, ev.getMessage.getContent, prefix)) - .filterWhen(_ => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels .filter(channel => { MCChatUtils.resetLastMessage(channel) recevents.add(ev)