diff --git a/pom.xml b/pom.xml index 0ccfe46..270da7f 100755 --- a/pom.xml +++ b/pom.xml @@ -1,251 +1,251 @@ - - 4.0.0 - - com.github.TBMCPlugins - DiscordPlugin - master-SNAPSHOT - jar - - DiscordPlugin - http://maven.apache.org - - - - src/main/java - - - src - - **/*.java - - - - src/main/resources - - *.properties - *.yml - *.csv - *.txt - - true - - - DiscordPlugin - - - maven-compiler-plugin - 3.6.2 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.2 - - - package - - shade - - - - - org.spigotmc:spigot-api - com.github.TBMCPlugins.ButtonCore:ButtonCore - net.ess3:Essentials - - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.1 - - - copy - compile - - copy-resources - - - target - - - resources - - - - - - - - - maven-surefire-plugin - - false - - - - - - - - UTF-8 - - master - - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - jcenter - http://jcenter.bintray.com - - - jitpack.io - https://jitpack.io - - - vault-repo - http://nexus.hc.to/content/repositories/pub_releases - - - Essentials - http://repo.ess3.net/content/repositories/essrel/ - - - projectlombok.org - http://projectlombok.org/mavenrepo - - - - - - - junit - junit - 3.8.1 - test - - - org.spigotmc - spigot-api - 1.12-R0.1-SNAPSHOT - provided - - - org.spigotmc - spigot - 1.12.2-R0.1-SNAPSHOT - provided - - - - - com.github.SizableShrimp - Discord4J - httprequestchange-SNAPSHOT - - - - org.slf4j - slf4j-jdk14 - 1.7.21 - - - com.github.TBMCPlugins.ButtonCore - ButtonCore - ${branch}-SNAPSHOT - provided - - - com.github.milkbowl - VaultAPI - master-SNAPSHOT - provided - - - net.ess3 - Essentials - 2.13.1 - provided - - - com.github.xaanit - D4J-OAuth - master-SNAPSHOT - - - - org.projectlombok - lombok - 1.16.16 - provided - - - - - org.objenesis - objenesis - 2.6 - test - - - com.vdurmont - emoji-java - 4.0.0 - - - - - - ci - - - env.TRAVIS_BRANCH - - - - - ${env.TRAVIS_BRANCH} - - - - + + 4.0.0 + + com.github.TBMCPlugins + DiscordPlugin + master-SNAPSHOT + jar + + DiscordPlugin + http://maven.apache.org + + + + src/main/java + + + src + + **/*.java + + + + src/main/resources + + *.properties + *.yml + *.csv + *.txt + + true + + + DiscordPlugin + + + maven-compiler-plugin + 3.6.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.2 + + + package + + shade + + + + + org.spigotmc:spigot-api + com.github.TBMCPlugins.ButtonCore:ButtonCore + net.ess3:Essentials + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.1 + + + copy + compile + + copy-resources + + + target + + + resources + + + + + + + + + maven-surefire-plugin + + false + + + + + + + + UTF-8 + + master + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jcenter + http://jcenter.bintray.com + + + jitpack.io + https://jitpack.io + + + + Essentials + http://repo.ess3.net/content/repositories/essrel/ + + + projectlombok.org + http://projectlombok.org/mavenrepo + + + + + + + junit + junit + 3.8.1 + test + + + org.spigotmc + spigot-api + 1.12-R0.1-SNAPSHOT + provided + + + org.spigotmc + spigot + 1.12.2-R0.1-SNAPSHOT + provided + + + + + com.github.SizableShrimp + Discord4J + httprequestchange-SNAPSHOT + + + + org.slf4j + slf4j-jdk14 + 1.7.21 + + + com.github.TBMCPlugins.ButtonCore + ButtonCore + ${branch}-SNAPSHOT + provided + + + com.github.milkbowl + VaultAPI + master-SNAPSHOT + provided + + + net.ess3 + Essentials + 2.13.1 + provided + + + com.github.xaanit + D4J-OAuth + master-SNAPSHOT + + + + org.projectlombok + lombok + 1.16.16 + provided + + + + + org.objenesis + objenesis + 2.6 + test + + + com.vdurmont + emoji-java + 4.0.0 + + + + + + ci + + + env.TRAVIS_BRANCH + + + + + ${env.TRAVIS_BRANCH} + + + + diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index 6b649ab..c21cceb 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -1,9 +1,13 @@ package buttondevteam.discordplugin; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.IHaveConfig; +import lombok.val; import org.bukkit.Bukkit; import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IGuild; import sx.blah.discord.handle.obj.IIDLinkedObject; import sx.blah.discord.handle.obj.IRole; import sx.blah.discord.util.EmbedBuilder; @@ -58,7 +62,7 @@ public final class DPUtils { return null; if (Bukkit.isPrimaryThread()) // TODO: Ignore shutdown message <-- // throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag."); - Bukkit.getLogger().warning("Waiting for a Discord request on the main thread!"); + getLogger().warning("Waiting for a Discord request on the main thread!"); return RequestBuffer.request(action).get(timeout, unit); // Let the pros handle this } @@ -71,7 +75,7 @@ public final class DPUtils { return null; if (Bukkit.isPrimaryThread()) // TODO: Ignore shutdown message <-- // throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag."); - Bukkit.getLogger().warning("Waiting for a Discord request on the main thread!"); + getLogger().warning("Waiting for a Discord request on the main thread!"); return RequestBuffer.request(action).get(); // Let the pros handle this } @@ -112,8 +116,16 @@ public final class DPUtils { return config.getDataPrimDef(key, defID, id -> DiscordPlugin.dc.getChannelByID((long) id), IIDLinkedObject::getLongID); //We can afford to search for the channel in the cache once (instead of using mainServer) } - public static ConfigData roleData(IHaveConfig config, String key, long defID) { - return config.getDataPrimDef(key, defID, id -> DiscordPlugin.dc.getRoleByID((long) id), IIDLinkedObject::getLongID); //We can afford to search for the channel in the cache once (instead of using mainServer) + public static ConfigData roleData(IHaveConfig config, String key, String defName) { + return roleData(config, key, defName, DiscordPlugin.mainServer); + } + + public static ConfigData roleData(IHaveConfig config, String key, String defName, IGuild guild) { + return config.getDataPrimDef(key, defName, name -> { + if (!(name instanceof String)) return null; + val roles = guild.getRolesByName((String) name); + return roles.size() > 0 ? roles.get(0) : null; + }, IIDLinkedObject::getLongID); } /** @@ -122,8 +134,38 @@ public final class DPUtils { * @return The string for mentioning the channel */ public static String botmention() { - if (DiscordPlugin.plugin == null) return "#bot"; - return DiscordPlugin.plugin.CommandChannel().get().mention(); + IChannel channel; + if (DiscordPlugin.plugin == null + || (channel = DiscordPlugin.plugin.CommandChannel().get()) == null) return "#bot"; + return channel.mention(); + } + + /** + * Disables the component if one of the given configs return null. Useful for channel/role configs. + * + * @param component The component to disable if needed + * @param configs The configs to check for null + * @return Whether the component got disabled and a warning logged + */ + public static boolean disableIfConfigError(@Nullable Component component, ConfigData... configs) { + for (val config : configs) { + if (config.get() == null) { + String path = null; + try { + if (component != null) + Component.setComponentEnabled(component, false); + val f = ConfigData.class.getDeclaredField("path"); + f.setAccessible(true); //Hacking my own plugin + path = (String) f.get(config); + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to disable component after config error!", e); + } + getLogger().warning("The config value " + path + " isn't set correctly " + (component == null ? "in global settings!" : "for component " + component.getClass().getSimpleName() + "!")); + getLogger().warning("Set the correct ID in the config" + (component == null ? "" : " or disable this component") + " to remove this message."); + return true; + } + } + return false; } } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 9187d52..6d71070 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -1,8 +1,10 @@ package buttondevteam.discordplugin; +import buttondevteam.discordplugin.announcer.AnnouncerModule; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; -import buttondevteam.discordplugin.commands.DiscordCommandBase; +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.MCChatPrivate; @@ -18,9 +20,11 @@ import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import com.google.common.io.Files; +import lombok.Getter; import lombok.val; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.scheduler.BukkitTask; @@ -40,12 +44,15 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class DiscordPlugin extends ButtonPlugin implements IListener { public static IDiscordClient dc; public static DiscordPlugin plugin; public static boolean SafeMode = true; + @Getter + private Command2DC manager; public ConfigData Prefix() { return getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString); @@ -64,13 +71,35 @@ public class DiscordPlugin extends ButtonPlugin implements IListener return DPUtils.channelData(getIConfig(), "commandChannel", 239519012529111040L); } + public ConfigData ModRole() { + return DPUtils.roleData(getIConfig(), "modRole", "Moderator"); + } + @Override public void pluginEnable() { try { - Bukkit.getLogger().info("Initializing DiscordPlugin..."); + getLogger().info("Initializing..."); plugin = this; + manager = new Command2DC(); ClientBuilder cb = new ClientBuilder(); - cb.withToken(Files.readFirstLine(new File("TBMC", "Token.txt"), StandardCharsets.UTF_8)); + File tokenFile = new File("TBMC", "Token.txt"); + if (tokenFile.exists()) //Legacy support + //noinspection UnstableApiUsage + cb.withToken(Files.readFirstLine(tokenFile, StandardCharsets.UTF_8)); + else { + File privateFile = new File(getDataFolder(), "private.yml"); + val conf = YamlConfiguration.loadConfiguration(privateFile); + String token = conf.getString("token"); + if (token == null) { + conf.set("token", "Token goes here"); + conf.save(privateFile); + + getLogger().severe("Token not found! Set it in private.yml"); + Bukkit.getPluginManager().disablePlugin(this); + return; + } else + cb.withToken(token); + } dc = cb.login(); dc.getDispatcher().registerListener(this); } catch (Exception e) { @@ -79,20 +108,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener } } - public static IChannel botchannel; //Can be removed - public static IChannel annchannel; - public static IChannel genchannel; - public static IChannel chatchannel; - public static IChannel botroomchannel; - public static IChannel modlogchannel; - /** - * Don't send messages, just receive, the same channel is used when testing - */ - public static IChannel officechannel; - public static IChannel updatechannel; - public static IChannel devofficechannel; public static IGuild mainServer; - public static IGuild devServer; private static volatile BukkitTask task; private static volatile boolean sent = false; @@ -101,51 +117,49 @@ public class DiscordPlugin extends ButtonPlugin implements IListener public void handle(ReadyEvent event) { try { dc.changePresence(StatusType.DND, ActivityType.PLAYING, "booting"); + val tries = new AtomicInteger(); task = Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> { - if (mainServer == null || devServer == null) { - mainServer = event.getClient().getGuildByID(125813020357165056L); - devServer = event.getClient().getGuildByID(219529124321034241L); - } - if (mainServer == null || devServer == null) - return; // Retry + tries.incrementAndGet(); + if (tries.get() > 10) { //5 seconds + task.cancel(); + getLogger().severe("Main server not found! Invite the bot and do /discord reset"); + //getIConfig().getConfig().set("mainServer", 219529124321034241L); //Needed because it won't save as long as it's null - made it save + saveConfig(); //Put default there + return; + } + mainServer = MainServer().get(); //Shouldn't change afterwards + if (mainServer == null) { + val guilds = dc.getGuilds(); + if (guilds.size() == 0) + return; //If there are no guilds in cache, retry + mainServer = guilds.get(0); + getLogger().warning("Main server set to first one: " + mainServer.getName()); + MainServer().set(mainServer); //Save in config + } if (!TBMCCoreAPI.IsTestServer()) { //Don't change conditions here, see mainServer=devServer=null in onDisable() - botchannel = mainServer.getChannelByID(209720707188260864L); // bot - annchannel = mainServer.getChannelByID(126795071927353344L); // announcements - genchannel = mainServer.getChannelByID(125813020357165056L); // general - chatchannel = mainServer.getChannelByID(249663564057411596L); // minecraft_chat - botroomchannel = devServer.getChannelByID(239519012529111040L); // bot-room - officechannel = devServer.getChannelByID(219626707458457603L); // developers-office - updatechannel = devServer.getChannelByID(233724163519414272L); // server-updates - devofficechannel = officechannel; // developers-office - modlogchannel = mainServer.getChannelByID(283840717275791360L); // modlog - dc.changePresence(StatusType.ONLINE, ActivityType.PLAYING, "Chromacraft"); + dc.changePresence(StatusType.ONLINE, ActivityType.PLAYING, "Minecraft"); } else { - botchannel = devServer.getChannelByID(239519012529111040L); // bot-room - annchannel = botchannel; // bot-room - genchannel = botchannel; // bot-room - botroomchannel = botchannel;// bot-room - chatchannel = botchannel;// bot-room - officechannel = devServer.getChannelByID(219626707458457603L); // developers-office - updatechannel = botchannel; - devofficechannel = botchannel;// bot-room - modlogchannel = botchannel; // bot-room dc.changePresence(StatusType.ONLINE, ActivityType.PLAYING, "testing"); } - if (botchannel == null || annchannel == null || genchannel == null || botroomchannel == null - || chatchannel == null || officechannel == null || updatechannel == null) - return; // Retry SafeMode = false; if (task != null) task.cancel(); if (!sent) { + DPUtils.disableIfConfigError(null, CommandChannel(), ModRole()); //Won't disable, just prints the warning here + 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()); new ChromaBot(this).updatePlayerList(); //Initialize ChromaBot - The MCCHatModule is tested to be enabled - DiscordCommandBase.registerCommands(); + getManager().registerCommand(new VersionCommand()); + getManager().registerCommand(new UserinfoCommand()); + getManager().registerCommand(new HelpCommand()); + getManager().registerCommand(new DebugCommand()); + getManager().registerCommand(new ConnectCommand()); if (ResetMCCommand.resetting) //These will only execute if the chat is enabled ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.CYAN) .withTitle("Discord plugin restarted - chat connected.").build(), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm @@ -196,6 +210,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener @Override public void pluginPreDisable() { + if (ChromaBot.getInstance() == null) return; //Failed to load EmbedObject embed; if (ResetMCCommand.resetting) embed = new EmbedBuilder().withColor(Color.ORANGE).withTitle("Discord plugin restarting").build(); @@ -226,6 +241,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener public void pluginDisable() { MCChatPrivate.logoutAll(); getConfig().set("serverup", false); + if (ChromaBot.getInstance() == null) return; //Failed to load saveConfig(); try { @@ -233,7 +249,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener ChromaBot.delete(); dc.changePresence(StatusType.IDLE, ActivityType.PLAYING, "Chromacraft"); //No longer using the same account for testing dc.logout(); - mainServer = devServer = null; //Fetch servers and channels again + //Configs are emptied so channels and servers are fetched again sent = false; } catch (Exception e) { TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e); @@ -273,7 +289,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException { if (message.length() > 1980) { message = message.substring(0, 1980); - Bukkit.getLogger() + DPUtils.getLogger() .warning("Message was too long to send to discord and got truncated. In " + channel.getName()); } try { @@ -296,7 +312,7 @@ public class DiscordPlugin extends ButtonPlugin implements IListener } catch (TimeoutException | InterruptedException e) { throw e; } catch (Exception e) { - Bukkit.getLogger().warning( + DPUtils.getLogger().warning( "Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message); throw new RuntimeException(e); } diff --git a/src/main/java/buttondevteam/discordplugin/AnnouncerModule.java b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java similarity index 93% rename from src/main/java/buttondevteam/discordplugin/AnnouncerModule.java rename to src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java index 866635f..c434dce 100644 --- a/src/main/java/buttondevteam/discordplugin/AnnouncerModule.java +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java @@ -1,5 +1,8 @@ -package buttondevteam.discordplugin; +package buttondevteam.discordplugin.announcer; +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlayer; +import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; @@ -16,7 +19,7 @@ import sx.blah.discord.handle.obj.IMessage; import java.io.File; import java.util.List; -public class AnnouncerModule extends Component { +public class AnnouncerModule extends Component { public ConfigData channel() { return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); } @@ -45,6 +48,7 @@ public class AnnouncerModule extends Component { @Override protected void enable() { + if (DPUtils.disableIfConfigError(this, channel(), modChannel())) return; stop = false; //If not the first time DPUtils.performNoWait(() -> { try { diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java index 8830f37..9b57812 100644 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java @@ -1,19 +1,19 @@ package buttondevteam.discordplugin.broadcaster; import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; import lombok.Getter; -import org.bukkit.Bukkit; -public class GeneralEventBroadcasterModule extends Component { +public class GeneralEventBroadcasterModule extends Component { private static @Getter boolean hooked = false; @Override protected void enable() { try { PlayerListWatcher.hookUp(); - Bukkit.getLogger().info("Finished hooking into the player list"); + DPUtils.getLogger().info("Finished hooking into the player list"); hooked = true; } catch (Exception e) { TBMCCoreAPI.SendException("Error while hacking the player list!", e); diff --git a/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java deleted file mode 100644 index df9398a..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java +++ /dev/null @@ -1,110 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.core.component.channel.Channel; -import buttondevteam.discordplugin.*; -import buttondevteam.discordplugin.mcchat.MCChatCustom; -import buttondevteam.lib.player.TBMCPlayer; -import lombok.val; -import org.bukkit.Bukkit; -import sx.blah.discord.handle.obj.IMessage; -import sx.blah.discord.handle.obj.Permissions; -import sx.blah.discord.util.PermissionUtils; - -import java.util.Arrays; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -public class ChannelconCommand extends DiscordCommandBase { - @Override - public String getCommandName() { - return "channelcon"; - } - - @Override - public boolean run(IMessage message, String args) { - if (args.length() == 0) - return false; - if (!PermissionUtils.hasPermissions(message.getChannel(), message.getAuthor(), Permissions.MANAGE_CHANNEL)) { - message.reply("you need to have manage permissions for this channel!"); - return true; - } - if (MCChatCustom.hasCustomChat(message.getChannel())) { - if (args.toLowerCase().startsWith("remove")) { - if (MCChatCustom.removeCustomChat(message.getChannel())) - message.reply("channel connection removed."); - else - message.reply("wait what, couldn't remove channel connection."); - return true; - } - if (args.toLowerCase().startsWith("toggle")) { - val cc = MCChatCustom.getCustomChat(message.getChannel()); - assert cc != null; //It's not null - Supplier togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n")); - String[] argsa = args.split(" "); - if (argsa.length < 2) { - message.reply("toggles:\n" + togglesString.get()); - return true; - } - String arg = argsa[1].toUpperCase(); - val b = Arrays.stream(ChannelconBroadcast.values()).filter(t -> t.toString().equals(arg)).findAny(); - if (!b.isPresent()) { - message.reply("cannot find toggle. Toggles:\n" + togglesString.get()); - return true; - } - //A B | F - //------- A: original - B: mask - F: new - //0 0 | 0 - //0 1 | 1 - //1 0 | 1 - //1 1 | 0 - // XOR - cc.toggles ^= b.get().flag; - message.reply("'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")); - return true; - } - message.reply("this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it."); - return true; - } - val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(args) || (Arrays.stream(ch.IDs().get()).anyMatch(cid -> cid.equalsIgnoreCase(args)))).findAny(); - if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) - message.reply("MC channel with ID '" + args + "' not found! The ID is the command for it without the /."); - return true; - } - val dp = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class); - val chp = dp.getAs(TBMCPlayer.class); - if (chp == null) { - message.reply("you need to connect your Minecraft account. On our server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect "); - return true; - } - DiscordConnectedPlayer dcp = new DiscordConnectedPlayer(message.getAuthor(), message.getChannel(), chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName()); - //Using a fake player with no login/logout, should be fine for this event - String groupid = chan.get().getGroupID(dcp); - if (groupid == null) { - message.reply("sorry, that didn't work. You cannot use that Minecraft channel."); - return true; - } - /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { - message.reply("sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm."); - return true; - }*/ //TODO: "Channel admins" that can connect channels? - MCChatCustom.addCustomChat(message.getChannel(), groupid, chan.get(), message.getAuthor(), dcp, 0); - message.reply("alright, connection made to group `" + groupid + "`!"); - return true; - } - - @Override - public String[] getHelpText() { - return new String[]{ // - "---- Channel connect ---", // - "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", // - "You need to have access to the MC channel and have manage permissions on the Discord channel.", // - "You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect .", // - "Call this command from the channel you want to use.", // - "Usage: @" + DiscordPlugin.dc.getOurUser().getName() + " channelcon ", // - "Use the ID (command) of the channel, for example `g` for the global chat.", // - "To remove a connection use @ChromaBot channelcon remove in the channel.", // - "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", // - "Invite link: " // - }; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java b/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java new file mode 100644 index 0000000..a0b8fda --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java @@ -0,0 +1,17 @@ +package buttondevteam.discordplugin.commands; + +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.chat.Command2; + +public class Command2DC extends Command2 { + @Override + public void registerCommand(ICommand2DC command) { + super.registerCommand(command, DiscordPlugin.getPrefix()); //Needs to be configurable for the helps + } + + @Override + public boolean hasPermission(Command2DCSender sender, ICommand2DC command) { + //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.ModRole().get()); //TODO: ModRole may be null; more customisable way? + return true; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java new file mode 100644 index 0000000..bdff52d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java @@ -0,0 +1,25 @@ +package buttondevteam.discordplugin.commands; + +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.lib.chat.Command2Sender; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import sx.blah.discord.handle.obj.IMessage; + +@RequiredArgsConstructor +public class Command2DCSender implements Command2Sender { + private final @Getter IMessage message; + + @Override + public void sendMessage(String message) { + if (message.length() == 0) return; + message = DPUtils.sanitizeString(message); + message = Character.toLowerCase(message.charAt(0)) + message.substring(1); + this.message.reply(message); + } + + @Override + public void sendMessage(String[] message) { + sendMessage(String.join("\n", message)); + } +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java index 6e16c09..4e37e35 100755 --- a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java @@ -1,76 +1,62 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.player.TBMCPlayer; -import buttondevteam.lib.player.TBMCPlayerBase; -import com.google.common.collect.HashBiMap; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import sx.blah.discord.handle.obj.IMessage; - -public class ConnectCommand extends DiscordCommandBase { - - @Override - public String getCommandName() { - return "connect"; - } - - /** - * Key: Minecraft name
- * Value: Discord ID - */ - public static HashBiMap WaitingToConnect = HashBiMap.create(); - - @Override - public boolean run(IMessage message, String args) { - if (args.length() == 0) - return false; - if (args.contains(" ")) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Too many arguments.\nUsage: " + DiscordPlugin.getPrefix() + "connect "); - return true; - } - if (WaitingToConnect.inverse().containsKey(message.getAuthor().getStringID())) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Replacing " + WaitingToConnect.inverse().get(message.getAuthor().getStringID()) + " with " + args); - WaitingToConnect.inverse().remove(message.getAuthor().getStringID()); - } - @SuppressWarnings("deprecation") - OfflinePlayer p = Bukkit.getOfflinePlayer(args); - if (p == null) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), "The specified Minecraft player cannot be found"); - return true; - } - try (TBMCPlayer pl = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class)) { - DiscordPlayer dp = pl.getAs(DiscordPlayer.class); - if (dp != null && message.getAuthor().getStringID().equals(dp.getDiscordID())) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), "You already have this account connected."); - return true; - } - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while connecting a Discord account!", e); - DiscordPlugin.sendMessageToChannel(message.getChannel(), "An internal error occured!\n" + e); - } - WaitingToConnect.put(p.getName(), message.getAuthor().getStringID()); - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Alright! Now accept the connection in Minecraft from the account " + args - + " before the next server restart. You can also adjust the Minecraft name you want to connect to with the same command."); - if (p.isOnline()) - ((Player) p).sendMessage("§bTo connect with the Discord account " + message.getAuthor().getName() + "#" - + message.getAuthor().getDiscriminator() + " do /discord accept"); - return true; - } - - @Override - public String[] getHelpText() { - return new String[] { // - "---- Connect command ----", // - "This command lets you connect your account with a Minecraft account. This allows using the Minecraft chat and other things.", // - "Usage: /connect " // - }; - } - -} +package buttondevteam.discordplugin.commands; + +import buttondevteam.discordplugin.DiscordPlayer; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.player.TBMCPlayer; +import buttondevteam.lib.player.TBMCPlayerBase; +import com.google.common.collect.HashBiMap; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +@CommandClass(helpText = { + "Connect command", // + "This command lets you connect your account with a Minecraft account. This allows using the Minecraft chat and other things.", // +}) +public class ConnectCommand extends ICommand2DC { + + /** + * Key: Minecraft name
+ * Value: Discord ID + */ + public static HashBiMap WaitingToConnect = HashBiMap.create(); + + @Command2.Subcommand + public boolean def(Command2DCSender sender, String Minecraftname) { + val message = sender.getMessage(); + if (WaitingToConnect.inverse().containsKey(message.getAuthor().getStringID())) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "Replacing " + WaitingToConnect.inverse().get(message.getAuthor().getStringID()) + " with " + Minecraftname); + WaitingToConnect.inverse().remove(message.getAuthor().getStringID()); + } + @SuppressWarnings("deprecation") + OfflinePlayer p = Bukkit.getOfflinePlayer(Minecraftname); + if (p == null) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), "The specified Minecraft player cannot be found"); + return true; + } + try (TBMCPlayer pl = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class)) { + DiscordPlayer dp = pl.getAs(DiscordPlayer.class); + if (dp != null && message.getAuthor().getStringID().equals(dp.getDiscordID())) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), "You already have this account connected."); + return true; + } + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occured while connecting a Discord account!", e); + DiscordPlugin.sendMessageToChannel(message.getChannel(), "An internal error occured!\n" + e); + } + WaitingToConnect.put(p.getName(), message.getAuthor().getStringID()); + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "Alright! Now accept the connection in Minecraft from the account " + Minecraftname + + " before the next server restart. You can also adjust the Minecraft name you want to connect to with the same command."); + if (p.isOnline()) + ((Player) p).sendMessage("§bTo connect with the Discord account " + message.getAuthor().getName() + "#" + + message.getAuthor().getDiscriminator() + " do /discord accept"); + return true; + } + +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java index b37e2b8..2cf87fb 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java @@ -2,25 +2,19 @@ package buttondevteam.discordplugin.commands; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.listeners.CommonListeners; -import sx.blah.discord.handle.obj.IMessage; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; -public class DebugCommand extends DiscordCommandBase { - @Override - public String getCommandName() { - return "debug"; - } - - @Override - public boolean run(IMessage message, String args) { - if (message.getAuthor().hasRole(DiscordPlugin.mainServer.getRoleByID(126030201472811008L))) - message.reply("Debug " + (CommonListeners.debug() ? "enabled" : "disabled")); +@CommandClass(helpText = { + "Switches debug mode." +}) +public class DebugCommand extends ICommand2DC { + @Command2.Subcommand + public boolean def(Command2DCSender sender, String args) { + if (sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.ModRole().get())) + sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled")); else - message.reply("You need to be a moderator to use this command."); + sender.sendMessage("you need to be a moderator to use this command."); return true; } - - @Override - public String[] getHelpText() { - return new String[]{"Switches debug mode."}; - } } diff --git a/src/main/java/buttondevteam/discordplugin/commands/DiscordCommandBase.java b/src/main/java/buttondevteam/discordplugin/commands/DiscordCommandBase.java deleted file mode 100755 index 947db5b..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/DiscordCommandBase.java +++ /dev/null @@ -1,62 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.mcchat.MCChatCommand; -import buttondevteam.lib.TBMCCoreAPI; -import sx.blah.discord.handle.obj.IMessage; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.stream.Collectors; - -import static buttondevteam.discordplugin.listeners.CommonListeners.debug; - -public abstract class DiscordCommandBase { - public abstract String getCommandName(); - - public abstract boolean run(IMessage message, String args); - - public abstract String[] getHelpText(); - - static final HashMap commands = new HashMap(); - - public static void registerCommands() { - commands.put("connect", new ConnectCommand()); // TODO: API for adding commands? - commands.put("userinfo", new UserinfoCommand()); - commands.put("help", new HelpCommand()); - commands.put("mcchat", new MCChatCommand()); - commands.put("channelcon", new ChannelconCommand()); - commands.put("debug", new DebugCommand()); - commands.put("version", new VersionCommand()); - } - - public static void runCommand(String cmd, String args, IMessage message) { - debug("F"); //Not sure if needed - DiscordCommandBase command = commands.get(cmd); - if (command == null) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Unknown command: " + cmd + " with args: " + args + "\nDo '" - + (message.getChannel().isPrivate() ? "" : message.getClient().getOurUser().mention() + " ") - + "help' for help"); - return; - } - debug("G"); - try { - if (!command.run(message, args)) - DiscordPlugin.sendMessageToChannel(message.getChannel(), Arrays.stream(command.getHelpText()).collect(Collectors.joining("\n"))); - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while executing command " + cmd + "!", e); - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "An internal error occured while executing this command. For more technical details see the server-issues channel on the dev Discord."); - } - debug("H"); - } - - protected String[] splitargs(String args) { - return args.split("\\s+"); - } - - public static void registerCommand(String name, DiscordCommandBase dcb) { - commands.put(name, dcb); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java index bcef6ea..0194dab 100755 --- a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java @@ -1,38 +1,18 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import sx.blah.discord.handle.obj.IMessage; - -import java.util.stream.Collectors; - -public class HelpCommand extends DiscordCommandBase { - - @Override - public String getCommandName() { - return "help"; - } - - @Override - public boolean run(IMessage message, String args) { - DiscordCommandBase argdc; - if (args.length() == 0) - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Available commands:\n" + DiscordCommandBase.commands.values().stream() - .map(dc -> DiscordPlugin.getPrefix() + dc.getCommandName()).collect(Collectors.joining("\n"))); - else - DiscordPlugin.sendMessageToChannel(message.getChannel(), - (argdc = DiscordCommandBase.commands.get(args)) == null ? "Command not found: " + args - : String.join("\n", argdc.getHelpText())); - return true; - } - - @Override - public String[] getHelpText() { - return new String[] { // - "---- Help command ----", // - "Shows some info about a command or lists the available commands.", // - "Usage: " + DiscordPlugin.getPrefix() + "help [command]"// - }; - } - -} +package buttondevteam.discordplugin.commands; + +import buttondevteam.lib.chat.CommandClass; + +@CommandClass(helpText = { + "Help command", // + "Shows some info about a command or lists the available commands.", // +}) +public class HelpCommand extends ICommand2DC { + @Override + public boolean def(Command2DCSender sender, String args) { + if (args.length() == 0) + sender.sendMessage(getManager().getCommandsText()); + else + sender.sendMessage("Soon:tm:"); //TODO + return true; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java b/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java new file mode 100644 index 0000000..6aae802 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java @@ -0,0 +1,20 @@ +package buttondevteam.discordplugin.commands; + +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.chat.ICommand2; +import lombok.Getter; +import lombok.val; + +public abstract class ICommand2DC extends ICommand2 { + public ICommand2DC() { + super(DiscordPlugin.plugin.getManager()); + val ann = getClass().getAnnotation(CommandClass.class); + if (ann == null) + modOnly = false; + else + modOnly = ann.modOnly(); + } + + private final @Getter boolean modOnly; +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java index b1dbf8b..0433c81 100755 --- a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java @@ -1,99 +1,91 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.player.ChromaGamerBase; -import buttondevteam.lib.player.ChromaGamerBase.InfoTarget; -import sx.blah.discord.handle.obj.IMessage; -import sx.blah.discord.handle.obj.IUser; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -public class UserinfoCommand extends DiscordCommandBase { - - @Override - public String getCommandName() { - return "userinfo"; - } - - @Override - public boolean run(IMessage message, String args) { - IUser target = null; - if (args.length() == 0) - target = message.getAuthor(); - else { - final Optional firstmention = message.getMentions().stream() - .filter(m -> !m.getStringID().equals(DiscordPlugin.dc.getOurUser().getStringID())).findFirst(); - if (firstmention.isPresent()) - target = firstmention.get(); - else if (args.contains("#")) { - String[] targettag = args.split("#"); - final List targets = getUsers(message, targettag[0]); - if (targets.size() == 0) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "The user cannot be found (by name): " + args); - return true; - } - for (IUser ptarget : targets) { - if (ptarget.getDiscriminator().equalsIgnoreCase(targettag[1])) { - target = ptarget; - break; - } - } - if (target == null) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "The user cannot be found (by discriminator): " + args + "(Found " + targets.size() - + " users with the name.)"); - return true; - } - } else { - final List targets = getUsers(message, args); - if (targets.size() == 0) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "The user cannot be found on Discord: " + args); - return true; - } - if (targets.size() > 1) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping."); - return true; - } - target = targets.get(0); - } - } - try (DiscordPlayer dp = ChromaGamerBase.getUser(target.getStringID(), DiscordPlayer.class)) { - StringBuilder uinfo = new StringBuilder("User info for ").append(target.getName()).append(":\n"); - uinfo.append(dp.getInfo(InfoTarget.Discord)); - DiscordPlugin.sendMessageToChannel(message.getChannel(), uinfo.toString()); - } catch (Exception e) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), "An error occured while getting the user!"); - TBMCCoreAPI.SendException("Error while getting info about " + target.getName() + "!", e); - } - return true; - } - - private List getUsers(IMessage message, String args) { - final List targets; - if (message.getChannel().isPrivate()) - targets = DiscordPlugin.dc.getUsers().stream().filter(u -> u.getName().equalsIgnoreCase(args)) - .collect(Collectors.toList()); - else - targets = message.getGuild().getUsersByName(args, true); - return targets; - } - - @Override - public String[] getHelpText() { - return new String[] { // - "---- User information ----", // - "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", // - "If used without args, shows your info.", // - "Usage: " + DiscordPlugin.getPrefix() + "userinfo [username/nickname[#tag]/ping]", // - "Examples:\n" + DiscordPlugin.getPrefix() + "userinfo ChromaBot\n" + DiscordPlugin.getPrefix() + "userinfo ChromaBot#6338\n" + DiscordPlugin.getPrefix() + "userinfo @ChromaBot#6338" // - }; - } - -} +package buttondevteam.discordplugin.commands; + +import buttondevteam.discordplugin.DiscordPlayer; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.player.ChromaGamerBase; +import buttondevteam.lib.player.ChromaGamerBase.InfoTarget; +import lombok.val; +import sx.blah.discord.handle.obj.IMessage; +import sx.blah.discord.handle.obj.IUser; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@CommandClass(helpText = { + "User information", // + "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", // + "If used without args, shows your info.", // +}) +public class UserinfoCommand extends ICommand2DC { + @Command2.Subcommand + public boolean def(Command2DCSender sender, @Command2.OptionalArg @Command2.TextArg String user) { + val message = sender.getMessage(); + IUser target = null; + if (user == null || user.length() == 0) + target = message.getAuthor(); + else { + final Optional firstmention = message.getMentions().stream() + .filter(m -> !m.getStringID().equals(DiscordPlugin.dc.getOurUser().getStringID())).findFirst(); + if (firstmention.isPresent()) + target = firstmention.get(); + else if (user.contains("#")) { + String[] targettag = user.split("#"); + final List targets = getUsers(message, targettag[0]); + if (targets.size() == 0) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "The user cannot be found (by name): " + user); + return true; + } + for (IUser ptarget : targets) { + if (ptarget.getDiscriminator().equalsIgnoreCase(targettag[1])) { + target = ptarget; + break; + } + } + if (target == null) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "The user cannot be found (by discriminator): " + user + "(Found " + targets.size() + + " users with the name.)"); + return true; + } + } else { + final List targets = getUsers(message, user); + if (targets.size() == 0) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "The user cannot be found on Discord: " + user); + return true; + } + if (targets.size() > 1) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), + "Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping."); + return true; + } + target = targets.get(0); + } + } + try (DiscordPlayer dp = ChromaGamerBase.getUser(target.getStringID(), DiscordPlayer.class)) { + StringBuilder uinfo = new StringBuilder("User info for ").append(target.getName()).append(":\n"); + uinfo.append(dp.getInfo(InfoTarget.Discord)); + DiscordPlugin.sendMessageToChannel(message.getChannel(), uinfo.toString()); + } catch (Exception e) { + DiscordPlugin.sendMessageToChannel(message.getChannel(), "An error occured while getting the user!"); + TBMCCoreAPI.SendException("Error while getting info about " + target.getName() + "!", e); + } + return true; + } + + private List getUsers(IMessage message, String args) { + final List targets; + if (message.getChannel().isPrivate()) + targets = DiscordPlugin.dc.getUsers().stream().filter(u -> u.getName().equalsIgnoreCase(args)) + .collect(Collectors.toList()); + else + targets = message.getGuild().getUsersByName(args, true); + return targets; + } + +} diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java index 5908495..ac83243 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java @@ -1,26 +1,21 @@ package buttondevteam.discordplugin.commands; import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; import lombok.val; -import sx.blah.discord.handle.obj.IMessage; -public class VersionCommand extends DiscordCommandBase { - @Override - public String getCommandName() { - return "version"; - } - - @Override - public boolean run(IMessage message, String args) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), String.join("\n", getVersion())); +@CommandClass(helpText = { + "Version", + "Returns the plugin's version" +}) +public class VersionCommand extends ICommand2DC { + @Command2.Subcommand + public boolean def(Command2DCSender sender) { + sender.sendMessage(getVersion()); return true; } - @Override - public String[] getHelpText() { - return VersionCommand.getVersion(); //Heh - } - public static String[] getVersion() { val desc = DiscordPlugin.plugin.getDescription(); return new String[]{ // diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java index cca3e98..95c3cdb 100755 --- a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java +++ b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java @@ -1,31 +1,32 @@ -package buttondevteam.discordplugin.exceptions; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCDebugMessageEvent; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -public class DebugMessageListener implements Listener{ - @EventHandler - public void onDebugMessage(TBMCDebugMessageEvent e) { - SendMessage(e.getDebugMessage()); - e.setSent(); - } - - private static void SendMessage(String message) { - if (DiscordPlugin.SafeMode) - return; - try { - StringBuilder sb = new StringBuilder(); - sb.append("```").append("\n"); - if (message.length() > 2000) - message = message.substring(0, 2000); - sb.append(message).append("\n"); - sb.append("```"); - DiscordPlugin.sendMessageToChannel(DiscordPlugin.botroomchannel, sb.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - -} +package buttondevteam.discordplugin.exceptions; + +import buttondevteam.core.ComponentManager; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.TBMCDebugMessageEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class DebugMessageListener implements Listener{ + @EventHandler + public void onDebugMessage(TBMCDebugMessageEvent e) { + SendMessage(e.getDebugMessage()); + e.setSent(); + } + + private static void SendMessage(String message) { + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(ExceptionListenerModule.class)) + return; + try { + StringBuilder sb = new StringBuilder(); + sb.append("```").append("\n"); + if (message.length() > 2000) + message = message.substring(0, 2000); + sb.append(message).append("\n"); + sb.append("```"); + DiscordPlugin.sendMessageToChannel(ExceptionListenerModule.getChannel(), sb.toString()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + +} diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java index f491a24..5d21bb1 100755 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java @@ -1,76 +1,97 @@ -package buttondevteam.discordplugin.exceptions; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.TBMCExceptionEvent; -import buttondevteam.lib.architecture.Component; -import org.apache.commons.lang.exception.ExceptionUtils; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import sx.blah.discord.handle.obj.IRole; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public class ExceptionListenerModule extends Component implements Listener { - private List lastthrown = new ArrayList<>(); - private List lastsourcemsg = new ArrayList<>(); - - @EventHandler - public void onException(TBMCExceptionEvent e) { - if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass())) - return; - if (lastthrown.stream() - .anyMatch(ex -> Arrays.equals(e.getException().getStackTrace(), ex.getStackTrace()) - && (e.getException().getMessage() == null ? ex.getMessage() == null - : e.getException().getMessage().equals(ex.getMessage()))) // e.Exception.Message==ex.Message - && lastsourcemsg.contains(e.getSourceMessage())) - return; - SendException(e.getException(), e.getSourceMessage()); - if (lastthrown.size() >= 10) - lastthrown.remove(0); - if (lastsourcemsg.size() >= 10) - lastsourcemsg.remove(0); - lastthrown.add(e.getException()); - lastsourcemsg.add(e.getSourceMessage()); - e.setHandled(); - } - - private static IRole coderRole; - - private static void SendException(Throwable e, String sourcemessage) { - try { - if (coderRole == null) - coderRole = DiscordPlugin.devServer.getRolesByName("Coder").get(0); - StringBuilder sb = TBMCCoreAPI.IsTestServer() ? new StringBuilder() - : new StringBuilder(coderRole.mention()).append("\n"); - sb.append(sourcemessage).append("\n"); - sb.append("```").append("\n"); - String stackTrace = Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) - .filter(s -> !s.contains("\tat ") || s.contains("\tat buttondevteam.")) - .collect(Collectors.joining("\n")); - if (stackTrace.length() > 1800) - stackTrace = stackTrace.substring(0, 1800); - sb.append(stackTrace).append("\n"); - sb.append("```"); - DiscordPlugin.sendMessageToChannel(DiscordPlugin.botroomchannel, sb.toString()); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - @Override - protected void enable() { - Bukkit.getPluginManager().registerEvents(new ExceptionListenerModule(), getPlugin()); - TBMCCoreAPI.RegisterEventsForExceptions(new DebugMessageListener(), getPlugin()); - } - - @Override - protected void disable() { - - } -} +package buttondevteam.discordplugin.exceptions; + +import buttondevteam.core.ComponentManager; +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.TBMCExceptionEvent; +import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.architecture.ConfigData; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IGuild; +import sx.blah.discord.handle.obj.IRole; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ExceptionListenerModule extends Component implements Listener { + private List lastthrown = new ArrayList<>(); + private List lastsourcemsg = new ArrayList<>(); + + @EventHandler + public void onException(TBMCExceptionEvent e) { + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass())) + return; + if (lastthrown.stream() + .anyMatch(ex -> Arrays.equals(e.getException().getStackTrace(), ex.getStackTrace()) + && (e.getException().getMessage() == null ? ex.getMessage() == null + : e.getException().getMessage().equals(ex.getMessage()))) // e.Exception.Message==ex.Message + && lastsourcemsg.contains(e.getSourceMessage())) + return; + SendException(e.getException(), e.getSourceMessage()); + if (lastthrown.size() >= 10) + lastthrown.remove(0); + if (lastsourcemsg.size() >= 10) + lastsourcemsg.remove(0); + lastthrown.add(e.getException()); + lastsourcemsg.add(e.getSourceMessage()); + e.setHandled(); + } + + private static void SendException(Throwable e, String sourcemessage) { + if (instance == null) return; + try { + IChannel channel = getChannel(); + assert channel != null; + IRole coderRole = instance.pingRole(channel.getGuild()).get(); + StringBuilder sb = TBMCCoreAPI.IsTestServer() ? new StringBuilder() + : new StringBuilder(coderRole == null ? "" : coderRole.mention()).append("\n"); + sb.append(sourcemessage).append("\n"); + sb.append("```").append("\n"); + String stackTrace = Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) + .filter(s -> !s.contains("\tat ") || s.contains("\tat buttondevteam.")) + .collect(Collectors.joining("\n")); + if (stackTrace.length() > 1800) + stackTrace = stackTrace.substring(0, 1800); + sb.append(stackTrace).append("\n"); + sb.append("```"); + DiscordPlugin.sendMessageToChannel(channel, sb.toString()); //Instance isn't null here + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private static ExceptionListenerModule instance; + + public static IChannel getChannel() { + if (instance != null) return instance.channel().get(); + return null; + } + + private ConfigData channel() { + return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); + } + + private ConfigData pingRole(IGuild guild) { + return DPUtils.roleData(getConfig(), "pingRole", "Coder", guild); + } + + @Override + protected void enable() { + if (DPUtils.disableIfConfigError(this, channel())) return; + instance = this; + Bukkit.getPluginManager().registerEvents(new ExceptionListenerModule(), getPlugin()); + TBMCCoreAPI.RegisterEventsForExceptions(new DebugMessageListener(), getPlugin()); + } + + @Override + protected void disable() { + instance = null; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java index 9565a53..f5adf0d 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.fun; import buttondevteam.core.ComponentManager; +import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; @@ -12,9 +13,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent; -import sx.blah.discord.handle.obj.IMessage; -import sx.blah.discord.handle.obj.IRole; -import sx.blah.discord.handle.obj.StatusType; +import sx.blah.discord.handle.obj.*; import sx.blah.discord.util.EmbedBuilder; import java.util.ArrayList; @@ -24,9 +23,7 @@ import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; -public class FunModule extends Component implements Listener { - private static FunModule mod; - +public class FunModule extends Component implements Listener { private static final String[] serverReadyStrings = new String[]{"In one week from now", // Ali "Between now and the heat-death of the universe.", // Ghostise "Soon™", "Ask again this time next month", // Ghostise @@ -66,7 +63,6 @@ public class FunModule extends Component implements Listener { @Override protected void enable() { - mod = this; registerListener(this); } @@ -97,7 +93,7 @@ public class FunModule extends Component implements Listener { return true; //Handled } lastlistp = (short) Bukkit.getOnlinePlayers().size(); //Didn't handle - if (mod.serverReady().get()) { + if (fm.serverReady().get()) { if (!TBMCCoreAPI.IsTestServer() && Arrays.stream(serverReadyQuestions).anyMatch(msglowercased::contains)) { int next; @@ -116,11 +112,13 @@ public class FunModule extends Component implements Listener { ListC = 0; } - private ConfigData fullHouseDevRole() { - return getConfig().getDataPrimDef("fullHouseDevRole", "Developer", name -> { - val list = DiscordPlugin.devServer.getRolesByName((String) name); - return list.size() > 0 ? list.get(0) : null; - }, IRole::getName); + private ConfigData fullHouseDevRole(IGuild guild) { + return DPUtils.roleData(getConfig(), "fullHouseDevRole", "Developer", guild); + } + + + private ConfigData fullHouseChannel() { + return DPUtils.channelData(getConfig(), "fullHouseChannel", 219626707458457603L); } private static long lasttime = 0; @@ -128,17 +126,19 @@ public class FunModule extends Component implements Listener { public static void handleFullHouse(PresenceUpdateEvent event) { val fm = ComponentManager.getIfEnabled(FunModule.class); if (fm == null) return; - val devrole = fm.fullHouseDevRole().get(); + val channel = fm.fullHouseChannel().get(); + if (channel == null) return; + val devrole = fm.fullHouseDevRole(channel.getGuild()).get(); if (devrole == null) return; if (event.getOldPresence().getStatus().equals(StatusType.OFFLINE) && !event.getNewPresence().getStatus().equals(StatusType.OFFLINE) - && event.getUser().getRolesForGuild(DiscordPlugin.devServer).stream() + && event.getUser().getRolesForGuild(channel.getGuild()).stream() .anyMatch(r -> r.getLongID() == devrole.getLongID()) - && DiscordPlugin.devServer.getUsersByRole(devrole).stream() + && channel.getGuild().getUsersByRole(devrole).stream() .noneMatch(u -> u.getPresence().getStatus().equals(StatusType.OFFLINE)) && lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime()) && Calendar.getInstance().get(Calendar.DAY_OF_MONTH) % 5 == 0) { - DiscordPlugin.sendMessageToChannel(DiscordPlugin.devofficechannel, "Full house!", + DiscordPlugin.sendMessageToChannel(channel, "Full house!", new EmbedBuilder() .withImage( "https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png") diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java index b6fe5c6..4f99d35 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java @@ -1,7 +1,8 @@ package buttondevteam.discordplugin.listeners; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.DiscordCommandBase; +import buttondevteam.discordplugin.commands.Command2DCSender; +import buttondevteam.lib.TBMCCoreAPI; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IRole; @@ -20,7 +21,7 @@ public class CommandListener { final IChannel channel = message.getChannel(); if (!mentionedonly) { //mentionedonly conditions are in CommonListeners if (!message.getChannel().isPrivate() - && !(message.getContent().charAt(0) == DiscordPlugin.getPrefix() + && !(message.getContent().charAt(0) == DiscordPlugin.getPrefix() && channel.getStringID().equals(DiscordPlugin.plugin.CommandChannel().get().getStringID()))) // return false; message.getChannel().setTypingStatus(true); // Fun @@ -37,36 +38,36 @@ public class CommandListener { return false; } message.getChannel().setTypingStatus(true); - String cmdwithargsString = cmdwithargs.toString().trim(); //Remove spaces between mention and command - int index = cmdwithargsString.indexOf(" "); - String cmd; - String args; - if (index == -1) { - cmd = cmdwithargsString; - args = ""; - } else { - cmd = cmdwithargsString.substring(0, index); - args = cmdwithargsString.substring(index + 1).trim(); //In case there are multiple spaces + String cmdwithargsString = cmdwithargs.toString(); + try { + if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString)) + message.reply("Unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.\n" + cmdwithargsString); + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e); } - DiscordCommandBase.runCommand(cmd.toLowerCase(), args, message); message.getChannel().setTypingStatus(false); return true; } private static boolean checkanddeletemention(StringBuilder cmdwithargs, String mention, IMessage message) { if (message.getContent().startsWith(mention)) // TODO: Resolve mentions: Compound arguments, either a mention or text - if (cmdwithargs.length() > mention.length() + 1) - cmdwithargs.delete(0, - cmdwithargs.charAt(mention.length()) == ' ' ? mention.length() + 1 : mention.length()); - else - cmdwithargs.replace(0, cmdwithargs.length(), "help"); + 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, DiscordPlugin.getPrefix()); //Always use the prefix for processing + } else + cmdwithargs.replace(0, cmdwithargs.length(), DiscordPlugin.getPrefix() + "help"); else { - if (cmdwithargs.length() > 0 && cmdwithargs.charAt(0) == '/') - cmdwithargs.deleteCharAt(0); //Don't treat / as mention, mentions can be used in public mcchat - return false; + return false; //Don't treat / as mention, mentions can be used in public mcchat } if (cmdwithargs.length() == 0) - cmdwithargs.replace(0, cmdwithargs.length(), "help"); + cmdwithargs.replace(0, cmdwithargs.length(), DiscordPlugin.getPrefix() + "help"); return true; } } diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java index 9b60fc3..5350b32 100755 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.fun.FunModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.role.GameRoleModule; +import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; import lombok.val; import sx.blah.discord.api.events.IListener; @@ -35,16 +36,21 @@ public class CommonListeners { return; if (FunModule.executeMemes(event.getMessage())) return; - boolean handled = false; - if (event.getChannel().getLongID() == DiscordPlugin.plugin.CommandChannel().get().getLongID() //If mentioned, that's higher than chat + try { + boolean handled = false; + val commandChannel = DiscordPlugin.plugin.CommandChannel().get(); + if ((commandChannel != null && event.getChannel().getLongID() == commandChannel.getLongID()) //If mentioned, that's higher than chat || event.getMessage().getContent().contains("channelcon")) //Only 'channelcon' is allowed in other channels - handled = CommandListener.runCommand(event.getMessage(), true); //#bot is handled here - if (handled) return; - val mcchat = Component.getComponents().get(MinecraftChatModule.class); - if (mcchat != null && mcchat.isEnabled()) //ComponentManager.isEnabled() searches the component again - handled = ((MinecraftChatModule) mcchat).getListener().handleDiscord(event); //Also runs Discord commands in chat channels - if (!handled) - handled = CommandListener.runCommand(event.getMessage(), false); + handled = CommandListener.runCommand(event.getMessage(), true); //#bot is handled here + if (handled) return; + val mcchat = Component.getComponents().get(MinecraftChatModule.class); + if (mcchat != null && mcchat.isEnabled()) //ComponentManager.isEnabled() searches the component again + handled = ((MinecraftChatModule) mcchat).getListener().handleDiscord(event); //Also runs Discord commands in chat channels + if (!handled) + handled = CommandListener.runCommand(event.getMessage(), false); + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occured while handling a message!", e); + } } }, new IListener() { @Override diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java index 2f99b56..c23b8fa 100755 --- a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java @@ -5,21 +5,11 @@ import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.commands.ConnectCommand; import buttondevteam.lib.player.TBMCPlayerGetInfoEvent; import buttondevteam.lib.player.TBMCPlayerJoinEvent; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.event.Event; import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.plugin.AuthorNagException; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredListener; import sx.blah.discord.handle.obj.IUser; -import java.util.Arrays; -import java.util.logging.Level; - public class MCListener implements Listener { @EventHandler public void onPlayerJoin(TBMCPlayerJoinEvent e) { @@ -50,67 +40,4 @@ public class MCListener implements Listener { public void onServerCommand(ServerCommandEvent e) { DiscordPlugin.Restart = !e.getCommand().equalsIgnoreCase("stop"); // The variable is always true except if stopped } - - private static final String[] EXCLUDED_PLUGINS = {"ProtocolLib", "LibsDisguises", "JourneyMapServer"}; //TODO: Make configurable - - public static void callEventExcludingSome(Event event) { - callEventExcluding(event, false, EXCLUDED_PLUGINS); - } - - /** - * Calls an event with the given details. - *

- * This method only synchronizes when the event is not asynchronous. - * - * @param event Event details - * @param only Flips the operation and includes the listed plugins - * @param plugins The plugins to exclude. Not case sensitive. - */ - public static void callEventExcluding(Event event, boolean only, String... plugins) { // Copied from Spigot-API and modified a bit - if (event.isAsynchronous()) { - if (Thread.holdsLock(Bukkit.getPluginManager())) { - throw new IllegalStateException( - event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); - } - if (Bukkit.getServer().isPrimaryThread()) { - throw new IllegalStateException( - event.getEventName() + " cannot be triggered asynchronously from primary server thread."); - } - fireEventExcluding(event, only, plugins); - } else { - synchronized (Bukkit.getPluginManager()) { - fireEventExcluding(event, only, plugins); - } - } - } - - private static void fireEventExcluding(Event event, boolean only, String... plugins) { - HandlerList handlers = event.getHandlers(); // Code taken from SimplePluginManager in Spigot-API - RegisteredListener[] listeners = handlers.getRegisteredListeners(); - val server = Bukkit.getServer(); - - for (RegisteredListener registration : listeners) { - if (!registration.getPlugin().isEnabled() - || Arrays.stream(plugins).anyMatch(p -> only ^ p.equalsIgnoreCase(registration.getPlugin().getName()))) - continue; // Modified to exclude plugins - - try { - registration.callEvent(event); - } catch (AuthorNagException ex) { - Plugin plugin = registration.getPlugin(); - - if (plugin.isNaggable()) { - plugin.setNaggable(false); - - server.getLogger().log(Level.SEVERE, - String.format("Nag author(s): '%s' of '%s' about the following: %s", - plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(), - ex.getMessage())); - } - } catch (Throwable ex) { - server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " - + registration.getPlugin().getDescription().getFullName(), ex); - } - } - } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java new file mode 100644 index 0000000..2bd2bb6 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java @@ -0,0 +1,150 @@ +package buttondevteam.discordplugin.mcchat; + +import buttondevteam.core.component.channel.Channel; +import buttondevteam.core.component.channel.ChatRoom; +import buttondevteam.discordplugin.*; +import buttondevteam.discordplugin.commands.Command2DCSender; +import buttondevteam.discordplugin.commands.ICommand2DC; +import buttondevteam.lib.TBMCSystemChatEvent; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.player.TBMCPlayer; +import lombok.val; +import org.bukkit.Bukkit; +import sx.blah.discord.handle.obj.IMessage; +import sx.blah.discord.handle.obj.Permissions; +import sx.blah.discord.util.PermissionUtils; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +@CommandClass(helpText = {"Channel connect", // + "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", // + "You need to have access to the MC channel and have manage permissions on the Discord channel.", // + "You also need to have your Minecraft account connected. In #bot use /connect .", // + "Call this command from the channel you want to use.", // + "Usage: @Bot channelcon ", // + "Use the ID (command) of the channel, for example `g` for the global chat.", // + "To remove a connection use @ChromaBot channelcon remove in the channel.", // + "Mentioning the bot is needed in this case because the / prefix only works in #bot.", // + "Invite link: " // +}) +public class ChannelconCommand extends ICommand2DC { + @Command2.Subcommand + public boolean remove(Command2DCSender sender) { + val message = sender.getMessage(); + if (checkPerms(message)) return true; + if (MCChatCustom.removeCustomChat(message.getChannel())) + message.reply("channel connection removed."); + else + message.reply("this channel isn't connected."); + return true; + } + + @Command2.Subcommand + public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) { + val message = sender.getMessage(); + if (checkPerms(message)) return true; + val cc = MCChatCustom.getCustomChat(message.getChannel()); + if (cc == null) + return respond(sender, "this channel isn't connected."); + Supplier togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n")) + + "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream().map(target -> target.getName() + ": " + (cc.brtoggles.contains(target) ? "enabled" : "disabled")).collect(Collectors.joining("\n")); + if (toggle == null) { + message.reply("toggles:\n" + togglesString.get()); + return true; + } + String arg = toggle.toUpperCase(); + val b = Arrays.stream(ChannelconBroadcast.values()).filter(t -> t.toString().equals(arg)).findAny(); + if (!b.isPresent()) { + val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg); + if (bt == null) { + message.reply("cannot find toggle. Toggles:\n" + togglesString.get()); + return true; + } + final boolean add; + if (add = !cc.brtoggles.contains(bt)) + cc.brtoggles.add(bt); + else + cc.brtoggles.remove(bt); + return respond(sender, "'" + bt.getName() + "' " + (add ? "en" : "dis") + "abled"); + } + //A B | F + //------- A: original - B: mask - F: new + //0 0 | 0 + //0 1 | 1 + //1 0 | 1 + //1 1 | 0 + // XOR + cc.toggles ^= b.get().flag; + message.reply("'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")); + return true; + } + + @Command2.Subcommand + public boolean def(Command2DCSender sender, String channelID) { + val message = sender.getMessage(); + if (checkPerms(message)) return true; + if (MCChatCustom.hasCustomChat(message.getChannel())) + return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it."); + val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(channelID) || (Arrays.stream(ch.IDs().get()).anyMatch(cid -> cid.equalsIgnoreCase(channelID)))).findAny(); + if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) + message.reply("MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /."); + return true; + } + val dp = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class); + val chp = dp.getAs(TBMCPlayer.class); + if (chp == null) { + message.reply("you need to connect your Minecraft account. On our server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect "); + return true; + } + DiscordConnectedPlayer dcp = new DiscordConnectedPlayer(message.getAuthor(), message.getChannel(), chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName()); + //Using a fake player with no login/logout, should be fine for this event + String groupid = chan.get().getGroupID(dcp); + if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later + message.reply("sorry, you cannot use that Minecraft channel."); + return true; + } + if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well + message.reply("chat rooms are not supported yet."); + return true; + } + /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { + message.reply("sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm."); + return true; + }*/ //TODO: "Channel admins" that can connect channels? + MCChatCustom.addCustomChat(message.getChannel(), groupid, chan.get(), message.getAuthor(), dcp, 0, new HashSet<>()); + if (chan.get() instanceof ChatRoom) + message.reply("alright, connection made to the room!"); + else + message.reply("alright, connection made to group `" + groupid + "`!"); + return true; + } + + private boolean checkPerms(IMessage message) { + if (!PermissionUtils.hasPermissions(message.getChannel(), message.getAuthor(), Permissions.MANAGE_CHANNEL)) { + message.reply("you need to have manage permissions for this channel!"); + return true; + } + return false; + } + + @Override + public String[] getHelpText(Method method, Command2.Subcommand ann) { + return new String[]{ // + "Channel connect", // + "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", // + "You need to have access to the MC channel and have manage permissions on the Discord channel.", // + "You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect .", // + "Call this command from the channel you want to use.", // + "Usage: @" + DiscordPlugin.dc.getOurUser().getName() + " channelcon ", // + "Use the ID (command) of the channel, for example `g` for the global chat.", // + "To remove a connection use @ChromaBot channelcon remove in the channel.", // + "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", // + "Invite link: " // TODO: Set correct client ID + }; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java index 3c05f02..23af46f 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java @@ -2,44 +2,38 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.DiscordCommandBase; +import buttondevteam.discordplugin.commands.Command2DCSender; +import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.lib.TBMCCoreAPI; -import sx.blah.discord.handle.obj.IMessage; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; +import lombok.val; -public class MCChatCommand extends DiscordCommandBase { +@CommandClass(helpText = { + "MC Chat", + "This command enables or disables the Minecraft chat in private messages.", // + "It can be useful if you don't want your messages to be visible, for example when talking in a private channel.", // + "You can also run all of the ingame commands you have access to using this command, if you have your accounts connected." // +}) +public class MCChatCommand extends ICommand2DC { - @Override - public String getCommandName() { - return "mcchat"; - } - - @Override //TODO: Only register if module is enabled - public boolean run(IMessage message, String args) { + @Command2.Subcommand + public boolean def(Command2DCSender sender) { + val message = sender.getMessage(); if (!message.getChannel().isPrivate()) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "This command can only be issued in a direct message with the bot."); + message.reply("this command can only be issued in a direct message with the bot."); return true; } try (final DiscordPlayer user = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class)) { boolean mcchat = !user.isMinecraftChatEnabled(); MCChatPrivate.privateMCChat(message.getChannel(), mcchat, message.getAuthor(), user); - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "Minecraft chat " + (mcchat // - ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." // - : "disabled.")); + message.reply("Minecraft chat " + (mcchat // + ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." // + : "disabled.")); } catch (Exception e) { TBMCCoreAPI.SendException("Error while setting mcchat for user" + message.getAuthor().getName(), e); } return true; - } - - @Override - public String[] getHelpText() { - return new String[] { // - DiscordPlugin.getPrefix() + "mcchat enables or disables the Minecraft chat in private messages.", // - "It can be useful if you don't want your messages to be visible, for example when talking in a private channel.", // - "You can also run all of the ingame commands you have access to using this command, if you have your accounts connected." // - }; // TODO: Pin channel switching to indicate the current channel - } + } // TODO: Pin channel switching to indicate the current channel } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java index fe0769a..3d1b52f 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java @@ -1,7 +1,9 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.component.channel.Channel; +import buttondevteam.core.component.channel.ChatRoom; import buttondevteam.discordplugin.DiscordConnectedPlayer; +import buttondevteam.lib.TBMCSystemChatEvent; import lombok.NonNull; import lombok.val; import sx.blah.discord.handle.obj.IChannel; @@ -11,6 +13,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; public class MCChatCustom { /** @@ -18,8 +21,12 @@ public class MCChatCustom { */ static ArrayList lastmsgCustom = new ArrayList<>(); - public static void addCustomChat(IChannel channel, String groupid, Channel mcchannel, IUser user, DiscordConnectedPlayer dcp, int toggles) { - val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles); + public static void addCustomChat(IChannel channel, String groupid, Channel mcchannel, IUser user, DiscordConnectedPlayer dcp, int toggles, Set brtoggles) { + if (mcchannel instanceof ChatRoom) { + ((ChatRoom) mcchannel).joinRoom(dcp); + if (groupid == null) groupid = mcchannel.getGroupID(dcp); + } + val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles, brtoggles); lastmsgCustom.add(lmd); } @@ -34,7 +41,13 @@ public class MCChatCustom { public static boolean removeCustomChat(IChannel channel) { MCChatUtils.lastmsgfromd.remove(channel.getLongID()); - return lastmsgCustom.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID()); + return lastmsgCustom.removeIf(lmd -> { + if (lmd.channel.getLongID() != channel.getLongID()) + return false; + if (lmd.mcchannel instanceof ChatRoom) + ((ChatRoom) lmd.mcchannel).leaveRoom(lmd.dcp); + return true; + }); } public static List getCustomChats() { @@ -46,14 +59,16 @@ public class MCChatCustom { public final Channel mcchannel; public final DiscordConnectedPlayer dcp; public int toggles; + public Set brtoggles; private CustomLMD(@NonNull IChannel channel, @NonNull IUser user, - @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles) { + @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles, Set brtoggles) { super(channel, user); groupID = groupid; this.mcchannel = mcchannel; this.dcp = dcp; this.toggles = toggles; + this.brtoggles = brtoggles; } } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 6c90870..5113f3c 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -9,10 +9,7 @@ import buttondevteam.discordplugin.DiscordSender; import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.listeners.CommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; -import buttondevteam.lib.TBMCChatEvent; -import buttondevteam.lib.TBMCChatPreprocessEvent; -import buttondevteam.lib.TBMCCommandPreprocessEvent; -import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.*; import buttondevteam.lib.chat.ChatMessage; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.TBMCPlayer; @@ -377,7 +374,7 @@ public class MCChatListener implements Listener { : dsender.getChromaUser().channel().get().getRTR(dsender); TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel().get(), rtr, (dsender instanceof Player ? ((Player) dsender).getDisplayName() - : dsender.getName()) + " pinned a message on Discord."); + : dsender.getName()) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL); } else { val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java index 877d415..7d87376 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java @@ -15,8 +15,6 @@ import sx.blah.discord.handle.obj.IUser; import java.util.ArrayList; -import static buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome; - public class MCChatPrivate { /** @@ -60,11 +58,11 @@ public class MCChatPrivate { for (val entry : MCChatUtils.ConnectedSenders.entrySet()) for (val valueEntry : entry.getValue().entrySet()) if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out - callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync + MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync MCChatUtils.ConnectedSenders.clear(); } private static void callEventSync(Event event) { - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event)); + Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event)); } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index cd45dbe..3f04e56 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -8,17 +8,25 @@ import buttondevteam.lib.TBMCSystemChatEvent; import io.netty.util.collection.LongObjectHashMap; import lombok.RequiredArgsConstructor; import lombok.experimental.var; +import lombok.val; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.AuthorNagException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IUser; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.HashMap; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -172,7 +180,7 @@ public class MCChatUtils { if (event.shouldSendTo(getSender(data.channel, data.user))) action.accept(data.channel); MCChatCustom.lastmsgCustom.stream().filter(clmd -> { - if ((clmd.toggles & ChannelconBroadcast.BROADCAST.flag) == 0) + if (!clmd.brtoggles.contains(event.getTarget())) return false; return event.shouldSendTo(clmd.dcp); }).map(clmd -> clmd.channel).forEach(action); @@ -213,6 +221,69 @@ public class MCChatUtils { //If it gets here, it's sending a message to a non-chat channel } + public static void callEventExcludingSome(Event event) { + if (notEnabled()) return; + callEventExcluding(event, false, module.excludedPlugins().get()); + } + + /** + * Calls an event with the given details. + *

+ * This method only synchronizes when the event is not asynchronous. + * + * @param event Event details + * @param only Flips the operation and includes the listed plugins + * @param plugins The plugins to exclude. Not case sensitive. + */ + @SuppressWarnings("WeakerAccess") + public static void callEventExcluding(Event event, boolean only, String... plugins) { // Copied from Spigot-API and modified a bit + if (event.isAsynchronous()) { + if (Thread.holdsLock(Bukkit.getPluginManager())) { + throw new IllegalStateException( + event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); + } + if (Bukkit.getServer().isPrimaryThread()) { + throw new IllegalStateException( + event.getEventName() + " cannot be triggered asynchronously from primary server thread."); + } + fireEventExcluding(event, only, plugins); + } else { + synchronized (Bukkit.getPluginManager()) { + fireEventExcluding(event, only, plugins); + } + } + } + + private static void fireEventExcluding(Event event, boolean only, String... plugins) { + HandlerList handlers = event.getHandlers(); // Code taken from SimplePluginManager in Spigot-API + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + val server = Bukkit.getServer(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled() + || Arrays.stream(plugins).anyMatch(p -> only ^ p.equalsIgnoreCase(registration.getPlugin().getName()))) + continue; // Modified to exclude plugins + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + server.getLogger().log(Level.SEVERE, + String.format("Nag author(s): '%s' of '%s' about the following: %s", + plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(), + ex.getMessage())); + } + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + + registration.getPlugin().getDescription().getFullName(), ex); + } + } + } + @RequiredArgsConstructor public static class LastMsgData { public IMessage message; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index ca34d8c..3ed7bcf 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -3,8 +3,10 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.discordplugin.*; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; +import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.player.*; import com.earth2me.essentials.CommandSource; +import lombok.RequiredArgsConstructor; import lombok.val; import net.ess3.api.events.AfkStatusChangeEvent; import net.ess3.api.events.MuteStatusChangeEvent; @@ -26,14 +28,17 @@ import sx.blah.discord.handle.obj.IUser; import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.MissingPermissionsException; +@RequiredArgsConstructor class MCListener implements Listener { + private final MinecraftChatModule module; + @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerLogin(PlayerLoginEvent e) { if (e.getResult() != Result.ALLOWED) return; MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders - .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() - .ifPresent(dcp -> buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome(new PlayerQuitEvent(dcp, ""))); + .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() + .ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(dcp, ""))); } @EventHandler(priority = EventPriority.LOWEST) @@ -46,9 +51,9 @@ class MCListener implements Listener { if (dp != null) { val user = DiscordPlugin.dc.getUserByID(Long.parseLong(dp.getDiscordID())); MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), - new DiscordPlayerSender(user, user.getOrCreatePMChannel(), p)); + new DiscordPlayerSender(user, user.getOrCreatePMChannel(), p)); MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), - new DiscordPlayerSender(user, DiscordPlugin.chatchannel, p)); //Stored per-channel + new DiscordPlayerSender(user, module.chatChannel().get(), p)); //Stored per-channel } final String message = e.GetPlayer().PlayerName().get() + " joined the game"; MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); @@ -61,13 +66,13 @@ class MCListener implements Listener { if (e.getPlayer() instanceof DiscordConnectedPlayer) return; // Only care about real users MCChatUtils.OnlineSenders.entrySet() - .removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId()))); + .removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId()))); Bukkit.getScheduler().runTask(DiscordPlugin.plugin, - () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) - .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() - .ifPresent(dcp -> buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome(new PlayerJoinEvent(dcp, "")))); + () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) + .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() + .ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerJoinEvent(dcp, "")))); Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, - ChromaBot.getInstance()::updatePlayerList, 5); + ChromaBot.getInstance()::updatePlayerList, 5); final String message = e.GetPlayer().PlayerName().get() + " left the game"; MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); } @@ -90,32 +95,41 @@ class MCListener implements Listener { if (e.isCancelled() || !base.isOnline()) return; final String msg = base.getDisplayName() - + " is " + (e.getValue() ? "now" : "no longer") + " AFK."; + + " is " + (e.getValue() ? "now" : "no longer") + " AFK."; MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false); } + private ConfigData muteRole() { + return DPUtils.roleData(module.getConfig(), "muteRole", "Muted"); + } + @EventHandler public void onPlayerMute(MuteStatusChangeEvent e) { try { DPUtils.performNoWait(() -> { - final IRole role = DiscordPlugin.dc.getRoleByID(164090010461667328L); //TODO: Config + final IRole role = muteRole().get(); + if (role == null) return; final CommandSource source = e.getAffected().getSource(); if (!source.isPlayer()) return; final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class) - .getAs(DiscordPlayer.class); + .getAs(DiscordPlayer.class); if (p == null) return; final IUser user = DiscordPlugin.dc.getUserByID( - Long.parseLong(p.getDiscordID())); + Long.parseLong(p.getDiscordID())); if (e.getValue()) user.addRole(role); else user.removeRole(role); - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, (e.getValue() ? "M" : "Unm") + "uted user: " + user.getName()); + val modlog = module.modlogChannel().get(); + String msg = (e.getValue() ? "M" : "Unm") + "uted user: " + user.getName(); + if (modlog != null) + DiscordPlugin.sendMessageToChannel(modlog, msg); + DPUtils.getLogger().info(msg); }); } catch (DiscordException | MissingPermissionsException ex) { TBMCCoreAPI.SendException("Failed to give/take Muted role to player " + e.getAffected().getName() + "!", - ex); + ex); } } @@ -132,9 +146,10 @@ class MCListener implements Listener { @EventHandler public void onYEEHAW(TBMCYEEHAWEvent event) { //TODO: Inherit from the chat event base to have channel support String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName() - : event.getSender().getName(); + : event.getSender().getName(); //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO - MCChatUtils.forAllMCChat(MCChatUtils.send(name + " <:YEEHAW:" + DiscordPlugin.mainServer.getEmojiByName("YEEHAW").getStringID() + ">s")); + val yeehaw = DiscordPlugin.mainServer.getEmojiByName("YEEHAW"); + MCChatUtils.forAllMCChat(MCChatUtils.send(name + (yeehaw != null ? " <:YEEHAW:" + yeehaw.getStringID() + ">s" : " YEEHAWs"))); } @EventHandler diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index 55d68d6..509ea66 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import com.google.common.collect.Lists; @@ -14,30 +15,58 @@ import org.bukkit.Bukkit; import sx.blah.discord.handle.obj.IChannel; import java.util.ArrayList; +import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; -public class MinecraftChatModule extends Component { +/** + * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. + */ +public class MinecraftChatModule extends Component { private @Getter MCChatListener listener; public MCChatListener getListener() { //It doesn't want to generate return listener; } + /** + * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! + */ public ConfigData> whitelistedCommands() { return getConfig().getData("whitelistedCommands", () -> Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", "yeehaw", "lenny", "rp", "plugins")); } + /** + * The channel to use as the public Minecraft chat - everything public gets broadcasted here + */ public ConfigData chatChannel() { return DPUtils.channelData(getConfig(), "chatChannel", 239519012529111040L); } + /** + * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute + */ + public ConfigData modlogChannel() { + return DPUtils.channelData(getConfig(), "modlogChannel", 283840717275791360L); + } + + /** + * 0 * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here + */ + public ConfigData excludedPlugins() { + return getConfig().getData("excludedPlugins", new String[]{"ProtocolLib", "LibsDisguises", "JourneyMapServer"}); + } + @Override protected void enable() { + if (DPUtils.disableIfConfigError(this, chatChannel())) return; listener = new MCChatListener(this); DiscordPlugin.dc.getDispatcher().registerListener(listener); TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin()); - TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled + TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled + getPlugin().getManager().registerCommand(new MCChatCommand()); + getPlugin().getManager().registerCommand(new ChannelconCommand()); val chcons = getConfig().getConfig().getConfigurationSection("chcons"); if (chcons == null) //Fallback to old place @@ -52,11 +81,12 @@ public class MinecraftChatModule extends Component { val user = DiscordPlugin.dc.fetchUser(did); val groupid = chcon.getString("groupid"); val toggles = chcon.getInt("toggles"); + val brtoggles = chcon.getStringList("brtoggles"); if (!mcch.isPresent() || ch == null || user == null || groupid == null) continue; Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) val dcp = new DiscordConnectedPlayer(user, ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname")); - MCChatCustom.addCustomChat(ch, groupid, mcch.get(), user, dcp, toggles); + MCChatCustom.addCustomChat(ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet())); }); } } @@ -75,7 +105,8 @@ public class MinecraftChatModule extends Component { chconc.set("mcname", chcon.dcp.getName()); chconc.set("groupid", chcon.groupID); chconc.set("toggles", chcon.toggles); + chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList())); } MCChatListener.stop(true); - } //TODO: Use ComponentManager.isEnabled() at other places too, instead of SafeMode + } } diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/ReloadMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/ReloadMCCommand.java new file mode 100644 index 0000000..01433c9 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mccommands/ReloadMCCommand.java @@ -0,0 +1,26 @@ +package buttondevteam.discordplugin.mccommands; + +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.chat.TBMCCommandBase; +import org.bukkit.command.CommandSender; + +@CommandClass(path = "discord reload") +public class ReloadMCCommand extends TBMCCommandBase { + @Override + public boolean OnCommand(CommandSender sender, String alias, String[] args) { + if (DiscordPlugin.plugin.tryReloadConfig()) + sender.sendMessage("§bConfig reloaded."); //TODO: Convert to new command system + else + sender.sendMessage("§cFailed to reload config."); + return true; + } + + @Override + public String[] GetHelpText(String alias) { + return new String[]{ + "Reload", + "Reloads the config. To apply some changes, you may need to also run /discord reset." + }; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java index 7bf3d2b..8806647 100644 --- a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java +++ b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java @@ -3,7 +3,6 @@ package buttondevteam.discordplugin.role; import buttondevteam.core.ComponentManager; import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.DiscordCommandBase; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import lombok.val; @@ -19,12 +18,12 @@ import java.awt.*; import java.util.List; import java.util.stream.Collectors; -public class GameRoleModule extends Component { +public class GameRoleModule extends Component { public List GameRoles; @Override protected void enable() { - DiscordCommandBase.registerCommand("role", new RoleCommand(this)); + getPlugin().getManager().registerCommand(new RoleCommand(this)); GameRoles = DiscordPlugin.mainServer.getRoles().stream().filter(this::isGameRole).map(IRole::getName).collect(Collectors.toList()); } @@ -41,30 +40,34 @@ public class GameRoleModule extends Component { val grm = ComponentManager.getIfEnabled(GameRoleModule.class); if (grm == null) return; val GameRoles = grm.GameRoles; + val logChannel = grm.logChannel().get(); if (roleEvent instanceof RoleCreateEvent) { Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { if (roleEvent.getRole().isDeleted() || !grm.isGameRole(roleEvent.getRole())) return; //Deleted or not a game role GameRoles.add(roleEvent.getRole().getName()); - DiscordPlugin.sendMessageToChannel(grm.logChannel().get(), "Added " + roleEvent.getRole().getName() + " as game role. If you don't want this, change the role's color from the default."); + if (logChannel != null) + DiscordPlugin.sendMessageToChannel(logChannel, "Added " + roleEvent.getRole().getName() + " as game role. If you don't want this, change the role's color from the default."); }, 100); } else if (roleEvent instanceof RoleDeleteEvent) { - if (GameRoles.remove(roleEvent.getRole().getName())) - DiscordPlugin.sendMessageToChannel(grm.logChannel().get(), "Removed " + roleEvent.getRole().getName() + " as a game role."); + if (GameRoles.remove(roleEvent.getRole().getName()) && logChannel != null) + DiscordPlugin.sendMessageToChannel(logChannel, "Removed " + roleEvent.getRole().getName() + " as a game role."); } else if (roleEvent instanceof RoleUpdateEvent) { val event = (RoleUpdateEvent) roleEvent; if (!grm.isGameRole(event.getNewRole())) { - if (GameRoles.remove(event.getOldRole().getName())) - DiscordPlugin.sendMessageToChannel(grm.logChannel().get(), "Removed " + event.getOldRole().getName() + " as a game role because it's color changed."); + if (GameRoles.remove(event.getOldRole().getName()) && logChannel != null) + DiscordPlugin.sendMessageToChannel(logChannel, "Removed " + event.getOldRole().getName() + " as a game role because it's color changed."); } else { if (GameRoles.contains(event.getOldRole().getName()) && event.getOldRole().getName().equals(event.getNewRole().getName())) return; boolean removed = GameRoles.remove(event.getOldRole().getName()); //Regardless of whether it was a game role GameRoles.add(event.getNewRole().getName()); //Add it because it has no color - if (removed) - DiscordPlugin.sendMessageToChannel(grm.logChannel().get(), "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + "."); - else - DiscordPlugin.sendMessageToChannel(grm.logChannel().get(), "Added " + event.getNewRole().getName() + " as game role because it has the default color."); + if (logChannel != null) { + if (removed) + DiscordPlugin.sendMessageToChannel(logChannel, "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + "."); + else + DiscordPlugin.sendMessageToChannel(logChannel, "Added " + event.getNewRole().getName() + " as game role because it has the default color."); + } } } } @@ -74,6 +77,7 @@ public class GameRoleModule extends Component { return false; //Only allow on the main server val rc = new Color(149, 165, 166, 0); return r.getColor().equals(rc) - && r.getPosition() < DiscordPlugin.mainServer.getRoleByID(234343495735836672L).getPosition(); //Below the ChromaBot role + && DiscordPlugin.dc.getOurUser().getRolesForGuild(DiscordPlugin.mainServer) + .stream().anyMatch(or -> r.getPosition() < or.getPosition()); //Below one of our roles } } diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java index 1534e2b..19d78cf 100755 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java @@ -2,15 +2,18 @@ package buttondevteam.discordplugin.role; import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.DiscordCommandBase; +import buttondevteam.discordplugin.commands.Command2DCSender; +import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.lib.TBMCCoreAPI; -import sx.blah.discord.handle.obj.IMessage; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; import sx.blah.discord.handle.obj.IRole; import java.util.List; import java.util.stream.Collectors; -public class RoleCommand extends DiscordCommandBase { //TODO: Use Command2's parser +@CommandClass +public class RoleCommand extends ICommand2DC { private GameRoleModule grm; @@ -18,83 +21,64 @@ public class RoleCommand extends DiscordCommandBase { //TODO: Use Command2's par this.grm = grm; } - @Override - public String getCommandName() { - return "role"; + @Command2.Subcommand(helpText = { + "Add role", + "This command adds a role to your account." + }) + public boolean add(Command2DCSender sender, @Command2.TextArg String rolename) { + final IRole role = checkAndGetRole(sender, rolename); + if (role == null) + return true; + try { + DPUtils.perform(() -> sender.getMessage().getAuthor().addRole(role)); + sender.sendMessage("added role."); + } catch (Exception e) { + TBMCCoreAPI.SendException("Error while adding role!", e); + sender.sendMessage("an error occured while adding the role."); + } + return true; + } + + @Command2.Subcommand(helpText = { + "Remove role", + "This command removes a role from your account." + }) + public boolean remove(Command2DCSender sender, @Command2.TextArg String rolename) { + final IRole role = checkAndGetRole(sender, rolename); + if (role == null) + return true; + try { + DPUtils.perform(() -> sender.getMessage().getAuthor().removeRole(role)); + sender.sendMessage("removed role."); + } catch (Exception e) { + TBMCCoreAPI.SendException("Error while removing role!", e); + sender.sendMessage("an error occured while removing the role."); + } + return true; + } + + @Command2.Subcommand + public void list(Command2DCSender sender) { + sender.sendMessage("list of roles:\n" + grm.GameRoles.stream().sorted().collect(Collectors.joining("\n"))); } - @Override - public boolean run(IMessage message, String args) { - if (args.length() == 0) - return false; - String[] argsa = splitargs(args); - if (argsa[0].equalsIgnoreCase("add")) { - final IRole role = checkAndGetRole(message, argsa, "This command adds a role to your account."); - if (role == null) - return true; - try { - DPUtils.perform(() -> message.getAuthor().addRole(role)); - DiscordPlugin.sendMessageToChannel(message.getChannel(), "Added role."); - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while adding role!", e); - DiscordPlugin.sendMessageToChannel(message.getChannel(), "An error occured while adding the role."); - } - } else if (argsa[0].equalsIgnoreCase("remove")) { - final IRole role = checkAndGetRole(message, argsa, "This command removes a role from your account."); - if (role == null) - return true; - try { - DPUtils.perform(() -> message.getAuthor().removeRole(role)); - DiscordPlugin.sendMessageToChannel(message.getChannel(), "Removed role."); - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while removing role!", e); - DiscordPlugin.sendMessageToChannel(message.getChannel(), "An error occured while removing the role."); - } - } else if (argsa[0].equalsIgnoreCase("list")) { - listRoles(message); - } else return false; - return true; - } - - private void listRoles(IMessage message) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "List of roles:\n" + grm.GameRoles.stream().sorted().collect(Collectors.joining("\n"))); - } - - private IRole checkAndGetRole(IMessage message, String[] argsa, String usage) { - if (argsa.length < 2) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), usage + "\nUsage: " + argsa[0] + " "); + private IRole checkAndGetRole(Command2DCSender sender, String rolename) { + if (!grm.GameRoles.contains(rolename)) { + sender.sendMessage("that role cannot be found."); + list(sender); return null; } - StringBuilder rolename = new StringBuilder(argsa[1]); - for (int i = 2; i < argsa.length; i++) - rolename.append(" ").append(argsa[i]); - if (!grm.GameRoles.contains(rolename.toString())) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), "That role cannot be found."); - listRoles(message); - return null; - } - final List roles = DiscordPlugin.mainServer.getRolesByName(rolename.toString()); + final List roles = DiscordPlugin.mainServer.getRolesByName(rolename); if (roles.size() == 0) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "The specified role cannot be found on Discord! Removing from the list."); - grm.GameRoles.remove(rolename.toString()); + sender.sendMessage("the specified role cannot be found on Discord! Removing from the list."); + grm.GameRoles.remove(rolename); return null; } if (roles.size() > 1) { - DiscordPlugin.sendMessageToChannel(message.getChannel(), - "There are more roles with this name. Why are there more roles with this name?"); + sender.sendMessage("there are multiple roles with this name. Why are there multiple roles with this name?"); return null; } return roles.get(0); } - @Override - public String[] getHelpText() { - return new String[]{ // - "Add or remove roles from yourself.", // - "Usage: " + DiscordPlugin.getPrefix() + "role add|remove or role list", // - }; - } - }