diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4b9e6ec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = false +indent_style = space +indent_size = 4 + +[*.json] +indent_style = space +indent_size = 2 + +[*.java] +indent_style = tab +tab_width = 4 + +[{*.yml, *.yaml}] +indent_style = space +indent_size = 2 + diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index f68053a..6c079f6 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -1,6 +1,10 @@ package buttondevteam.discordplugin; +import buttondevteam.lib.architecture.ConfigData; +import buttondevteam.lib.architecture.IHaveConfig; import org.bukkit.Bukkit; +import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IIDLinkedObject; import sx.blah.discord.util.EmbedBuilder; import sx.blah.discord.util.RequestBuffer; import sx.blah.discord.util.RequestBuffer.IRequest; @@ -103,4 +107,18 @@ public final class DPUtils { return DiscordPlugin.plugin.getLogger(); } + public static ConfigData channelData(IHaveConfig config, String key, long defID) { + 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) + } + + /** + * Mentions the bot channel. Useful for help texts. + * + * @return The string for mentioning the channel + */ + public static String botmention() { + if (DiscordPlugin.plugin == null) return "#bot"; + return DiscordPlugin.plugin.CommandChannel().get().mention(); + } + } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 9870d99..557ecd4 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -1,450 +1,460 @@ -package buttondevteam.discordplugin; - -import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; -import buttondevteam.discordplugin.commands.DiscordCommandBase; -import buttondevteam.discordplugin.exceptions.ExceptionListenerModule; -import buttondevteam.discordplugin.listeners.CommonListeners; -import buttondevteam.discordplugin.listeners.MCListener; -import buttondevteam.discordplugin.mcchat.*; -import buttondevteam.discordplugin.mccommands.DiscordMCCommandBase; -import buttondevteam.discordplugin.mccommands.ResetMCCommand; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.ButtonPlugin; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.chat.Channel; -import buttondevteam.lib.chat.TBMCChatAPI; -import buttondevteam.lib.player.ChromaGamerBase; -import com.google.common.io.Files; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import lombok.val; -import net.milkbowl.vault.permission.Permission; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.scheduler.BukkitTask; -import sx.blah.discord.api.ClientBuilder; -import sx.blah.discord.api.IDiscordClient; -import sx.blah.discord.api.events.IListener; -import sx.blah.discord.api.internal.json.objects.EmbedObject; -import sx.blah.discord.handle.impl.events.ReadyEvent; -import sx.blah.discord.handle.impl.obj.ReactionEmoji; -import sx.blah.discord.handle.obj.*; -import sx.blah.discord.util.EmbedBuilder; -import sx.blah.discord.util.RequestBuffer; - -import java.awt.*; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -public class DiscordPlugin extends ButtonPlugin implements IListener { - private static final String SubredditURL = "https://www.reddit.com/r/ChromaGamers"; - private static boolean stop = false; - public static IDiscordClient dc; - public static DiscordPlugin plugin; - public static boolean SafeMode = true; - public static List GameRoles; - - public ConfigData Prefix() { - return getData("prefix", '/'); - } - - public static char getPrefix() { - if (plugin == null) return '/'; - return plugin.Prefix().get(); - } - - @Override - public void pluginEnable() { - stop = false; //If not the first time - try { - Bukkit.getLogger().info("Initializing DiscordPlugin..."); - plugin = this; - lastannouncementtime = getConfig().getLong("lastannouncementtime"); - lastseentime = getConfig().getLong("lastseentime"); - ClientBuilder cb = new ClientBuilder(); - cb.withToken(Files.readFirstLine(new File("TBMC", "Token.txt"), StandardCharsets.UTF_8)); - dc = cb.login(); - dc.getDispatcher().registerListener(this); - } catch (Exception e) { - e.printStackTrace(); - Bukkit.getPluginManager().disablePlugin(this); - } - } - - public static IChannel botchannel; - 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; - - @Override - public void handle(ReadyEvent event) { - try { - dc.changePresence(StatusType.DND, ActivityType.PLAYING, "booting"); - 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 - 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"); - } 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) { - new ChromaBot(this).updatePlayerList(); - GameRoles = mainServer.getRoles().stream().filter(this::isGameRole).map(IRole::getName).collect(Collectors.toList()); - - val chcons = getConfig().getConfigurationSection("chcons"); - if (chcons != null) { - val chconkeys = chcons.getKeys(false); - for (val chconkey : chconkeys) { - val chcon = chcons.getConfigurationSection(chconkey); - val mcch = Channel.getChannels().stream().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny(); - val ch = dc.getChannelByID(chcon.getLong("chid")); - val did = chcon.getLong("did"); - val user = dc.fetchUser(did); - val dcp = new DiscordConnectedPlayer(user, ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname")); - val groupid = chcon.getString("groupid"); - val toggles = chcon.getInt("toggles"); - if (!mcch.isPresent() || ch == null || user == null || groupid == null) - continue; - MCChatCustom.addCustomChat(ch, groupid, mcch.get(), user, dcp, toggles); - } - } - - DiscordCommandBase.registerCommands(); - if (ResetMCCommand.resetting) - ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.CYAN) - .withTitle("Discord plugin restarted - chat connected.").build(), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm - else if (getConfig().getBoolean("serverup", false)) { - ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.YELLOW) - .withTitle("Server recovered from a crash - chat connected.").build(), ChannelconBroadcast.RESTART); - val thr = new Throwable( - "The server shut down unexpectedly. See the log of the previous run for more details."); - thr.setStackTrace(new StackTraceElement[0]); - TBMCCoreAPI.SendException("The server crashed!", thr); - } else - ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.GREEN) - .withTitle("Server started - chat connected.").build(), ChannelconBroadcast.RESTART); - - ResetMCCommand.resetting = false; //This is the last event handling this flag - - getConfig().set("serverup", true); - saveConfig(); - DPUtils.performNoWait(() -> { - try { - List msgs = genchannel.getPinnedMessages(); - for (int i = msgs.size() - 1; i >= 10; i--) { // Unpin all pinned messages except the newest 10 - genchannel.unpin(msgs.get(i)); - Thread.sleep(10); - } - } catch (InterruptedException ignore) { - } - }); - sent = true; - if (TBMCCoreAPI.IsTestServer() && !dc.getOurUser().getName().toLowerCase().contains("test")) { - TBMCCoreAPI.SendException( - "Won't load because we're in testing mode and not using a separate account.", - new Exception( - "The plugin refuses to load until you change the token to a testing account.")); - Bukkit.getPluginManager().disablePlugin(this); - } - TBMCCoreAPI.SendUnsentExceptions(); - TBMCCoreAPI.SendUnsentDebugMessages(); - /*if (!TBMCCoreAPI.IsTestServer()) { - final Calendar currentCal = Calendar.getInstance(); - final Calendar newCal = Calendar.getInstance(); - currentCal.set(currentCal.get(Calendar.YEAR), currentCal.get(Calendar.MONTH), - currentCal.get(Calendar.DAY_OF_MONTH), 4, 10); - if (currentCal.get(Calendar.DAY_OF_MONTH) % 9 == 0 && currentCal.before(newCal)) { - Random rand = new Random(); - sendMessageToChannel(dc.getChannels().get(rand.nextInt(dc.getChannels().size())), - "You could make a religion out of this"); - } - }*/ - } - }, 0, 10); - for (IListener listener : CommonListeners.getListeners()) - dc.getDispatcher().registerListener(listener); - Component.registerComponent(this, new GeneralEventBroadcasterModule()); - Component.registerComponent(this, new MinecraftChatModule()); - Component.registerComponent(this, new ExceptionListenerModule()); - TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); - TBMCChatAPI.AddCommands(this, DiscordMCCommandBase.class); - TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class); - ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase - ? ((DiscordSenderBase) sender).getChromaUser() : null)); - new Thread(this::AnnouncementGetterThreadMethod).start(); - setupProviders(); - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while enabling DiscordPlugin!", e); - } - } - - public boolean isGameRole(IRole r) { - if (r.getGuild().getLongID() != mainServer.getLongID()) - return false; //Only allow on the main server - val rc = new Color(149, 165, 166, 0); - return r.getColor().equals(rc) - && r.getPosition() < mainServer.getRoleByID(234343495735836672L).getPosition(); //Below the ChromaBot role - } - - /** - * Always true, except when running "stop" from console - */ - public static boolean Restart; - - @Override - public void pluginDisable() { - stop = true; - MCChatPrivate.logoutAll(); - getConfig().set("lastannouncementtime", lastannouncementtime); - getConfig().set("lastseentime", lastseentime); - getConfig().set("serverup", false); - - val chcons = MCChatCustom.getCustomChats(); - val chconsc = getConfig().createSection("chcons"); - for (val chcon : chcons) { - val chconc = chconsc.createSection(chcon.channel.getStringID()); - chconc.set("mcchid", chcon.mcchannel.ID); - chconc.set("chid", chcon.channel.getLongID()); - chconc.set("did", chcon.user.getLongID()); - chconc.set("mcuid", chcon.dcp.getUniqueId().toString()); - chconc.set("mcname", chcon.dcp.getName()); - chconc.set("groupid", chcon.groupID); - chconc.set("toggles", chcon.toggles); - } - - saveConfig(); - EmbedObject embed; - if (ResetMCCommand.resetting) - embed = new EmbedBuilder().withColor(Color.ORANGE).withTitle("Discord plugin restarting").build(); - else - embed = new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED) - .withTitle(Restart ? "Server restarting" : "Server stopping") - .withDescription( - Bukkit.getOnlinePlayers().size() > 0 - ? (DPUtils - .sanitizeString(Bukkit.getOnlinePlayers().stream() - .map(Player::getDisplayName).collect(Collectors.joining(", "))) - + (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ") - + "kicked the hell out.") //TODO: Make configurable - : "") //If 'restart' is disabled then this isn't shown even if joinleave is enabled - .build(); - MCChatUtils.forCustomAndAllMCChat(ch -> { - try { - DiscordPlugin.sendMessageToChannelWait(ch, "", - embed, 5, TimeUnit.SECONDS); - } catch (TimeoutException | InterruptedException e) { - e.printStackTrace(); - } - }, ChannelconBroadcast.RESTART, false); - ChromaBot.getInstance().updatePlayerList(); - try { - SafeMode = true; // Stop interacting with Discord - MCChatListener.stop(true); - 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 - sent = false; - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e); - } - } - - private long lastannouncementtime = 0; - private long lastseentime = 0; - public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.of("✅"); - - private void AnnouncementGetterThreadMethod() { - while (!stop) { - try { - if (SafeMode) { - Thread.sleep(10000); - continue; - } - String body = TBMCCoreAPI.DownloadString(SubredditURL + "/new/.json?limit=10"); - JsonArray json = new JsonParser().parse(body).getAsJsonObject().get("data").getAsJsonObject() - .get("children").getAsJsonArray(); - StringBuilder msgsb = new StringBuilder(); - StringBuilder modmsgsb = new StringBuilder(); - long lastanntime = lastannouncementtime; - for (int i = json.size() - 1; i >= 0; i--) { - JsonObject item = json.get(i).getAsJsonObject(); - final JsonObject data = item.get("data").getAsJsonObject(); - String author = data.get("author").getAsString(); - JsonElement distinguishedjson = data.get("distinguished"); - String distinguished; - if (distinguishedjson.isJsonNull()) - distinguished = null; - else - distinguished = distinguishedjson.getAsString(); - String permalink = "https://www.reddit.com" + data.get("permalink").getAsString(); - long date = data.get("created_utc").getAsLong(); - if (date > lastseentime) - lastseentime = date; - else if (date > lastannouncementtime) { - do { - val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit"); - if (reddituserclass == null) - break; - val user = ChromaGamerBase.getUser(author, reddituserclass); - String id = user.getConnectedID(DiscordPlayer.class); - if (id != null) - author = "<@" + id + ">"; - } while (false); - if (!author.startsWith("<")) - author = "/u/" + author; - (distinguished != null && distinguished.equals("moderator") ? modmsgsb : msgsb) - .append("A new post was submitted to the subreddit by ").append(author).append("\n") - .append(permalink).append("\n"); - lastanntime = date; - } - } - if (msgsb.length() > 0) - genchannel.pin(sendMessageToChannelWait(genchannel, msgsb.toString())); - if (modmsgsb.length() > 0) - sendMessageToChannel(annchannel, modmsgsb.toString()); - if (lastannouncementtime != lastanntime) { - lastannouncementtime = lastanntime; // If sending succeeded - getConfig().set("lastannouncementtime", lastannouncementtime); - getConfig().set("lastseentime", lastseentime); - saveConfig(); - } - } catch (Exception e) { - e.printStackTrace(); - } - try { - Thread.sleep(10000); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - - public static void sendMessageToChannel(IChannel channel, String message) { - sendMessageToChannel(channel, message, null); - } - - public static void sendMessageToChannel(IChannel channel, String message, EmbedObject embed) { - try { - sendMessageToChannel(channel, message, embed, false); - } catch (TimeoutException | InterruptedException e) { - e.printStackTrace(); //Shouldn't happen, as we're not waiting on the result - } - } - - public static IMessage sendMessageToChannelWait(IChannel channel, String message) throws TimeoutException, InterruptedException { - return sendMessageToChannelWait(channel, message, null); - } - - public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed) throws TimeoutException, InterruptedException { - return sendMessageToChannel(channel, message, embed, true); - } - - public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException { - return sendMessageToChannel(channel, message, embed, true, timeout, unit); - } - - private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait) throws TimeoutException, InterruptedException { - return sendMessageToChannel(channel, message, embed, wait, -1, null); - } - - 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() - .warning("Message was too long to send to discord and got truncated. In " + channel.getName()); - } - try { - MCChatUtils.resetLastMessage(channel); // If this is a chat message, it'll be set again - final String content = message; - RequestBuffer.IRequest r = () -> embed == null ? channel.sendMessage(content) - : channel.sendMessage(content, embed, false); - if (wait) { - if (unit != null) - return DPUtils.perform(r, timeout, unit); - else - return DPUtils.perform(r); - } else { - if (unit != null) - plugin.getLogger().warning("Tried to set timeout for non-waiting call."); - else - DPUtils.performNoWait(r); - return null; - } - } catch (TimeoutException | InterruptedException e) { - throw e; - } catch (Exception e) { - Bukkit.getLogger().warning( - "Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message); - throw new RuntimeException(e); - } - } - - public static Permission perms; - - public boolean setupProviders() { - try { - Class.forName("net.milkbowl.vault.permission.Permission"); - Class.forName("net.milkbowl.vault.chat.Chat"); - } catch (ClassNotFoundException e) { - return false; - } - - RegisteredServiceProvider permsProvider = Bukkit.getServer().getServicesManager() - .getRegistration(Permission.class); - perms = permsProvider.getProvider(); - return perms != null; - } -} +package buttondevteam.discordplugin; + +import buttondevteam.component.channel.Channel; +import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; +import buttondevteam.discordplugin.commands.DiscordCommandBase; +import buttondevteam.discordplugin.exceptions.ExceptionListenerModule; +import buttondevteam.discordplugin.listeners.CommonListeners; +import buttondevteam.discordplugin.listeners.MCListener; +import buttondevteam.discordplugin.mcchat.*; +import buttondevteam.discordplugin.mccommands.DiscordMCCommandBase; +import buttondevteam.discordplugin.mccommands.ResetMCCommand; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.architecture.ButtonPlugin; +import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.architecture.ConfigData; +import buttondevteam.lib.chat.TBMCChatAPI; +import buttondevteam.lib.player.ChromaGamerBase; +import com.google.common.io.Files; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.val; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.scheduler.BukkitTask; +import sx.blah.discord.api.ClientBuilder; +import sx.blah.discord.api.IDiscordClient; +import sx.blah.discord.api.events.IListener; +import sx.blah.discord.api.internal.json.objects.EmbedObject; +import sx.blah.discord.handle.impl.events.ReadyEvent; +import sx.blah.discord.handle.impl.obj.ReactionEmoji; +import sx.blah.discord.handle.obj.*; +import sx.blah.discord.util.EmbedBuilder; +import sx.blah.discord.util.RequestBuffer; + +import java.awt.*; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +public class DiscordPlugin extends ButtonPlugin implements IListener { + private static final String SubredditURL = "https://www.reddit.com/r/ChromaGamers"; + private static boolean stop = false; + public static IDiscordClient dc; + public static DiscordPlugin plugin; + public static boolean SafeMode = true; + public static List GameRoles; + + public ConfigData Prefix() { + return getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString); + } + + public static char getPrefix() { + if (plugin == null) return '/'; + return plugin.Prefix().get(); + } + + public ConfigData MainServer() { + return getIConfig().getDataPrimDef("mainServer", 219529124321034241L, id -> dc.getGuildByID((long) id), IIDLinkedObject::getLongID); + } + + public ConfigData CommandChannel() { + return DPUtils.channelData(getIConfig(), "commandChannel", 239519012529111040L); + } + + @Override + public void pluginEnable() { + stop = false; //If not the first time + try { + Bukkit.getLogger().info("Initializing DiscordPlugin..."); + plugin = this; + lastannouncementtime = getConfig().getLong("lastannouncementtime"); + lastseentime = getConfig().getLong("lastseentime"); + ClientBuilder cb = new ClientBuilder(); + cb.withToken(Files.readFirstLine(new File("TBMC", "Token.txt"), StandardCharsets.UTF_8)); + dc = cb.login(); + dc.getDispatcher().registerListener(this); + } catch (Exception e) { + e.printStackTrace(); + Bukkit.getPluginManager().disablePlugin(this); + } + } + + 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; + + @Override + public void handle(ReadyEvent event) { + try { + dc.changePresence(StatusType.DND, ActivityType.PLAYING, "booting"); + 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 + 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"); + } 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) { + new ChromaBot(this).updatePlayerList(); + GameRoles = mainServer.getRoles().stream().filter(this::isGameRole).map(IRole::getName).collect(Collectors.toList()); + + val chcons = getConfig().getConfigurationSection("chcons"); + if (chcons != null) { + val chconkeys = chcons.getKeys(false); + for (val chconkey : chconkeys) { + val chcon = chcons.getConfigurationSection(chconkey); + val mcch = Channel.getChannels().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny(); + val ch = dc.getChannelByID(chcon.getLong("chid")); + val did = chcon.getLong("did"); + val user = dc.fetchUser(did); + val groupid = chcon.getString("groupid"); + val toggles = chcon.getInt("toggles"); + if (!mcch.isPresent() || ch == null || user == null || groupid == null) + continue; + Bukkit.getScheduler().runTask(this, () -> { //<-- 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); + }); + } + } + + DiscordCommandBase.registerCommands(); + if (ResetMCCommand.resetting) + ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.CYAN) + .withTitle("Discord plugin restarted - chat connected.").build(), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm + else if (getConfig().getBoolean("serverup", false)) { + ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.YELLOW) + .withTitle("Server recovered from a crash - chat connected.").build(), ChannelconBroadcast.RESTART); + val thr = new Throwable( + "The server shut down unexpectedly. See the log of the previous run for more details."); + thr.setStackTrace(new StackTraceElement[0]); + TBMCCoreAPI.SendException("The server crashed!", thr); + } else + ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.GREEN) + .withTitle("Server started - chat connected.").build(), ChannelconBroadcast.RESTART); + + ResetMCCommand.resetting = false; //This is the last event handling this flag + + getConfig().set("serverup", true); + saveConfig(); + DPUtils.performNoWait(() -> { + try { + List msgs = genchannel.getPinnedMessages(); + for (int i = msgs.size() - 1; i >= 10; i--) { // Unpin all pinned messages except the newest 10 + genchannel.unpin(msgs.get(i)); + Thread.sleep(10); + } + } catch (InterruptedException ignore) { + } + }); + sent = true; + if (TBMCCoreAPI.IsTestServer() && !dc.getOurUser().getName().toLowerCase().contains("test")) { + TBMCCoreAPI.SendException( + "Won't load because we're in testing mode and not using a separate account.", + new Exception( + "The plugin refuses to load until you change the token to a testing account. (The account needs to have \"test\" in it's name.)")); + Bukkit.getPluginManager().disablePlugin(this); + } + TBMCCoreAPI.SendUnsentExceptions(); + TBMCCoreAPI.SendUnsentDebugMessages(); + /*if (!TBMCCoreAPI.IsTestServer()) { + final Calendar currentCal = Calendar.getInstance(); + final Calendar newCal = Calendar.getInstance(); + currentCal.set(currentCal.get(Calendar.YEAR), currentCal.get(Calendar.MONTH), + currentCal.get(Calendar.DAY_OF_MONTH), 4, 10); + if (currentCal.get(Calendar.DAY_OF_MONTH) % 9 == 0 && currentCal.before(newCal)) { + Random rand = new Random(); + sendMessageToChannel(dc.getChannels().get(rand.nextInt(dc.getChannels().size())), + "You could make a religion out of this"); + } + }*/ + } + }, 0, 10); + for (IListener listener : CommonListeners.getListeners()) + dc.getDispatcher().registerListener(listener); + Component.registerComponent(this, new GeneralEventBroadcasterModule()); + Component.registerComponent(this, new MinecraftChatModule()); + Component.registerComponent(this, new ExceptionListenerModule()); + TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); + TBMCChatAPI.AddCommands(this, DiscordMCCommandBase.class); + TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class); + ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase + ? ((DiscordSenderBase) sender).getChromaUser() : null)); + new Thread(this::AnnouncementGetterThreadMethod).start(); + setupProviders(); + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occured while enabling DiscordPlugin!", e); + } + } + + public boolean isGameRole(IRole r) { + if (r.getGuild().getLongID() != mainServer.getLongID()) + return false; //Only allow on the main server + val rc = new Color(149, 165, 166, 0); + return r.getColor().equals(rc) + && r.getPosition() < mainServer.getRoleByID(234343495735836672L).getPosition(); //Below the ChromaBot role + } + + /** + * Always true, except when running "stop" from console + */ + public static boolean Restart; + + @Override + public void pluginDisable() { + stop = true; + MCChatPrivate.logoutAll(); + getConfig().set("lastannouncementtime", lastannouncementtime); + getConfig().set("lastseentime", lastseentime); + getConfig().set("serverup", false); + + val chcons = MCChatCustom.getCustomChats(); + val chconsc = getConfig().createSection("chcons"); + for (val chcon : chcons) { + val chconc = chconsc.createSection(chcon.channel.getStringID()); + chconc.set("mcchid", chcon.mcchannel.ID); + chconc.set("chid", chcon.channel.getLongID()); + chconc.set("did", chcon.user.getLongID()); + chconc.set("mcuid", chcon.dcp.getUniqueId().toString()); + chconc.set("mcname", chcon.dcp.getName()); + chconc.set("groupid", chcon.groupID); + chconc.set("toggles", chcon.toggles); + } + + saveConfig(); + EmbedObject embed; + if (ResetMCCommand.resetting) + embed = new EmbedBuilder().withColor(Color.ORANGE).withTitle("Discord plugin restarting").build(); + else + embed = new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED) + .withTitle(Restart ? "Server restarting" : "Server stopping") + .withDescription( + Bukkit.getOnlinePlayers().size() > 0 + ? (DPUtils + .sanitizeString(Bukkit.getOnlinePlayers().stream() + .map(Player::getDisplayName).collect(Collectors.joining(", "))) + + (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ") + + "kicked the hell out.") //TODO: Make configurable + : "") //If 'restart' is disabled then this isn't shown even if joinleave is enabled + .build(); + MCChatUtils.forCustomAndAllMCChat(ch -> { + try { + DiscordPlugin.sendMessageToChannelWait(ch, "", + embed, 5, TimeUnit.SECONDS); + } catch (TimeoutException | InterruptedException e) { + e.printStackTrace(); + } + }, ChannelconBroadcast.RESTART, false); + ChromaBot.getInstance().updatePlayerList(); + try { + SafeMode = true; // Stop interacting with Discord + MCChatListener.stop(true); + 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 + sent = false; + } catch (Exception e) { + TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e); + } + } + + private long lastannouncementtime = 0; + private long lastseentime = 0; + public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.of("✅"); + + private void AnnouncementGetterThreadMethod() { + while (!stop) { + try { + if (SafeMode) { + Thread.sleep(10000); + continue; + } + String body = TBMCCoreAPI.DownloadString(SubredditURL + "/new/.json?limit=10"); + JsonArray json = new JsonParser().parse(body).getAsJsonObject().get("data").getAsJsonObject() + .get("children").getAsJsonArray(); + StringBuilder msgsb = new StringBuilder(); + StringBuilder modmsgsb = new StringBuilder(); + long lastanntime = lastannouncementtime; + for (int i = json.size() - 1; i >= 0; i--) { + JsonObject item = json.get(i).getAsJsonObject(); + final JsonObject data = item.get("data").getAsJsonObject(); + String author = data.get("author").getAsString(); + JsonElement distinguishedjson = data.get("distinguished"); + String distinguished; + if (distinguishedjson.isJsonNull()) + distinguished = null; + else + distinguished = distinguishedjson.getAsString(); + String permalink = "https://www.reddit.com" + data.get("permalink").getAsString(); + long date = data.get("created_utc").getAsLong(); + if (date > lastseentime) + lastseentime = date; + else if (date > lastannouncementtime) { + do { + val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit"); + if (reddituserclass == null) + break; + val user = ChromaGamerBase.getUser(author, reddituserclass); + String id = user.getConnectedID(DiscordPlayer.class); + if (id != null) + author = "<@" + id + ">"; + } while (false); + if (!author.startsWith("<")) + author = "/u/" + author; + (distinguished != null && distinguished.equals("moderator") ? modmsgsb : msgsb) + .append("A new post was submitted to the subreddit by ").append(author).append("\n") + .append(permalink).append("\n"); + lastanntime = date; + } + } + if (msgsb.length() > 0) + genchannel.pin(sendMessageToChannelWait(genchannel, msgsb.toString())); + if (modmsgsb.length() > 0) + sendMessageToChannel(annchannel, modmsgsb.toString()); + if (lastannouncementtime != lastanntime) { + lastannouncementtime = lastanntime; // If sending succeeded + getConfig().set("lastannouncementtime", lastannouncementtime); + getConfig().set("lastseentime", lastseentime); + saveConfig(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + Thread.sleep(10000); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + + public static void sendMessageToChannel(IChannel channel, String message) { + sendMessageToChannel(channel, message, null); + } + + public static void sendMessageToChannel(IChannel channel, String message, EmbedObject embed) { + try { + sendMessageToChannel(channel, message, embed, false); + } catch (TimeoutException | InterruptedException e) { + e.printStackTrace(); //Shouldn't happen, as we're not waiting on the result + } + } + + public static IMessage sendMessageToChannelWait(IChannel channel, String message) throws TimeoutException, InterruptedException { + return sendMessageToChannelWait(channel, message, null); + } + + public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed) throws TimeoutException, InterruptedException { + return sendMessageToChannel(channel, message, embed, true); + } + + public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException { + return sendMessageToChannel(channel, message, embed, true, timeout, unit); + } + + private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait) throws TimeoutException, InterruptedException { + return sendMessageToChannel(channel, message, embed, wait, -1, null); + } + + 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() + .warning("Message was too long to send to discord and got truncated. In " + channel.getName()); + } + try { + MCChatUtils.resetLastMessage(channel); // If this is a chat message, it'll be set again + final String content = message; + RequestBuffer.IRequest r = () -> embed == null ? channel.sendMessage(content) + : channel.sendMessage(content, embed, false); + if (wait) { + if (unit != null) + return DPUtils.perform(r, timeout, unit); + else + return DPUtils.perform(r); + } else { + if (unit != null) + plugin.getLogger().warning("Tried to set timeout for non-waiting call."); + else + DPUtils.performNoWait(r); + return null; + } + } catch (TimeoutException | InterruptedException e) { + throw e; + } catch (Exception e) { + Bukkit.getLogger().warning( + "Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message); + throw new RuntimeException(e); + } + } + + public static Permission perms; + + public boolean setupProviders() { + try { + Class.forName("net.milkbowl.vault.permission.Permission"); + Class.forName("net.milkbowl.vault.chat.Chat"); + } catch (ClassNotFoundException e) { + return false; + } + + RegisteredServiceProvider permsProvider = Bukkit.getServer().getServicesManager() + .getRegistration(Permission.class); + perms = permsProvider.getProvider(); + return perms != null; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java index b031bcb..6d4098b 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java @@ -62,7 +62,7 @@ public abstract class DiscordSenderBase implements CommandSender { (!broadcast && user != null ? user.mention() + "\n" : "") + msgtosend.trim()); sendtask = null; msgtosend = ""; - }, 10); // Waits a half second to gather all/most of the different messages + }, 4); // Waits a 0.2 second to gather all/most of the different messages } catch (Exception e) { TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e); } diff --git a/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java index c89baab..8d20eb9 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/ChannelconCommand.java @@ -1,11 +1,8 @@ package buttondevteam.discordplugin.commands; -import buttondevteam.discordplugin.ChannelconBroadcast; -import buttondevteam.discordplugin.DiscordConnectedPlayer; -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.component.channel.Channel; +import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.mcchat.MCChatCustom; -import buttondevteam.lib.chat.Channel; import buttondevteam.lib.player.TBMCPlayer; import lombok.val; import org.bukkit.Bukkit; @@ -67,7 +64,7 @@ public class ChannelconCommand extends DiscordCommandBase { message.reply("this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it."); return true; } - val chan = Channel.getChannels().stream().filter(ch -> ch.ID.equalsIgnoreCase(args) || (ch.IDs != null && Arrays.stream(ch.IDs).anyMatch(cid -> cid.equalsIgnoreCase(args)))).findAny(); + 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; @@ -75,7 +72,7 @@ public class ChannelconCommand extends DiscordCommandBase { 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 #bot do /connect "); + 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()); @@ -100,12 +97,12 @@ public class ChannelconCommand extends DiscordCommandBase { "---- 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 " + DiscordPlugin.getPrefix() + "connect .", // + "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 #bot.", // + "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/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java index 5dea2d3..34d95e7 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java @@ -32,11 +32,11 @@ public class FunModule extends Component { }; private ConfigData serverReady() { - return getData("serverReady", true); + return getConfig().getData("serverReady", true); } private ConfigData> serverReadyAnswers() { - return getData("serverReadyAnswers", Arrays.asList(serverReadyStrings), + return getConfig().getData("serverReadyAnswers", Arrays.asList(serverReadyStrings), data -> (List) data, data -> data); //TODO: Test } diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java index 73e827d..b6fe5c6 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java @@ -21,7 +21,7 @@ public class CommandListener { if (!mentionedonly) { //mentionedonly conditions are in CommonListeners if (!message.getChannel().isPrivate() && !(message.getContent().charAt(0) == DiscordPlugin.getPrefix() - && channel.getStringID().equals(DiscordPlugin.botchannel.getStringID()))) // + && channel.getStringID().equals(DiscordPlugin.plugin.CommandChannel().get().getStringID()))) // return false; message.getChannel().setTypingStatus(true); // Fun } diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java index 7cc3dcc..b372750 100755 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java @@ -1,128 +1,128 @@ -package buttondevteam.discordplugin.listeners; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; -import buttondevteam.lib.architecture.Component; -import lombok.val; -import org.bukkit.Bukkit; -import sx.blah.discord.api.events.IListener; -import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; -import sx.blah.discord.handle.impl.events.guild.role.RoleCreateEvent; -import sx.blah.discord.handle.impl.events.guild.role.RoleDeleteEvent; -import sx.blah.discord.handle.impl.events.guild.role.RoleUpdateEvent; -import sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent; -import sx.blah.discord.handle.obj.StatusType; -import sx.blah.discord.util.EmbedBuilder; - -import java.util.Calendar; -import java.util.concurrent.TimeUnit; - -public class CommonListeners { - - /*private static ArrayList dcListeners=new ArrayList<>(); - - public static void registerDiscordListener(DiscordListener listener) { - //Step 1: Get all events that are handled by us - //Step 2: Find methods that handle these - //...or just simply call the methods in the right order - } - - private static void callDiscordEvent(Event event) { - String name=event.getClass().getSimpleName(); - name=Character.toLowerCase(name.charAt(0))+name.substring(1); - for (Object listener : dcListeners) { - listener.getClass().getMethods(name, AsyncDiscordEvent.class); - } - }*/ - - private static long lasttime = 0; - - /* - MentionEvent: - - CommandListener (starts with mention, only 'channelcon' and not in #bot) - - MessageReceivedEvent: - - v CommandListener (starts with mention, in #bot or a connected chat) - - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - - CommandListener (with the correct prefix in #bot, or in private) - */ - public static IListener[] getListeners() { - return new IListener[]{new IListener() { - @Override - public void handle(MessageReceivedEvent event) { - if (DiscordPlugin.SafeMode) - return; - if (event.getMessage().getAuthor().isBot()) - return; - boolean handled = false; - if (event.getChannel().getLongID() == DiscordPlugin.botchannel.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); - } - }, new IListener() { - @Override - public void handle(PresenceUpdateEvent event) { - if (DiscordPlugin.SafeMode) - return; - val devrole = DiscordPlugin.devServer.getRolesByName("Developer").get(0); - if (event.getOldPresence().getStatus().equals(StatusType.OFFLINE) - && !event.getNewPresence().getStatus().equals(StatusType.OFFLINE) - && event.getUser().getRolesForGuild(DiscordPlugin.devServer).stream() - .anyMatch(r -> r.getLongID() == devrole.getLongID()) - && DiscordPlugin.devServer.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!", - new EmbedBuilder() - .withImage( - "https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png") - .build()); - lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime()); - } - } - }, (IListener) event -> { - Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { - if (event.getRole().isDeleted() || !DiscordPlugin.plugin.isGameRole(event.getRole())) - return; //Deleted or not a game role - DiscordPlugin.GameRoles.add(event.getRole().getName()); - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Added " + event.getRole().getName() + " as game role. If you don't want this, change the role's color from the default."); - }, 100); - }, (IListener) event -> { - if (DiscordPlugin.GameRoles.remove(event.getRole().getName())) - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Removed " + event.getRole().getName() + " as a game role."); - }, (IListener) event -> { //Role update event - if (!DiscordPlugin.plugin.isGameRole(event.getNewRole())) { - if (DiscordPlugin.GameRoles.remove(event.getOldRole().getName())) - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Removed " + event.getOldRole().getName() + " as a game role because it's color changed."); - } else { - if (DiscordPlugin.GameRoles.contains(event.getOldRole().getName()) && event.getOldRole().getName().equals(event.getNewRole().getName())) - return; - boolean removed = DiscordPlugin.GameRoles.remove(event.getOldRole().getName()); //Regardless of whether it was a game role - DiscordPlugin.GameRoles.add(event.getNewRole().getName()); //Add it because it has no color - if (removed) - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + "."); - else - DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Added " + event.getNewRole().getName() + " as game role because it has the default color."); - } - }}; - } - - private static boolean debug = false; - - public static void debug(String debug) { - if (CommonListeners.debug) //Debug - DPUtils.getLogger().info(debug); - } - - public static boolean debug() { - return debug = !debug; - } -} +package buttondevteam.discordplugin.listeners; + +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.discordplugin.mcchat.MinecraftChatModule; +import buttondevteam.lib.architecture.Component; +import lombok.val; +import org.bukkit.Bukkit; +import sx.blah.discord.api.events.IListener; +import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; +import sx.blah.discord.handle.impl.events.guild.role.RoleCreateEvent; +import sx.blah.discord.handle.impl.events.guild.role.RoleDeleteEvent; +import sx.blah.discord.handle.impl.events.guild.role.RoleUpdateEvent; +import sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent; +import sx.blah.discord.handle.obj.StatusType; +import sx.blah.discord.util.EmbedBuilder; + +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +public class CommonListeners { + + /*private static ArrayList dcListeners=new ArrayList<>(); + + public static void registerDiscordListener(DiscordListener listener) { + //Step 1: Get all events that are handled by us + //Step 2: Find methods that handle these + //...or just simply call the methods in the right order + } + + private static void callDiscordEvent(Event event) { + String name=event.getClass().getSimpleName(); + name=Character.toLowerCase(name.charAt(0))+name.substring(1); + for (Object listener : dcListeners) { + listener.getClass().getMethods(name, AsyncDiscordEvent.class); + } + }*/ + + private static long lasttime = 0; + + /* + MentionEvent: + - CommandListener (starts with mention, only 'channelcon' and not in #bot) + + MessageReceivedEvent: + - v CommandListener (starts with mention, in #bot or a connected chat) + - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) + - CommandListener (with the correct prefix in #bot, or in private) + */ + public static IListener[] getListeners() { + return new IListener[]{new IListener() { + @Override + public void handle(MessageReceivedEvent event) { + if (DiscordPlugin.SafeMode) + return; + if (event.getMessage().getAuthor().isBot()) + return; + boolean handled = false; + if (event.getChannel().getLongID() == DiscordPlugin.plugin.CommandChannel().get().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); + } + }, new IListener() { + @Override + public void handle(PresenceUpdateEvent event) { + if (DiscordPlugin.SafeMode) + return; + val devrole = DiscordPlugin.devServer.getRolesByName("Developer").get(0); + if (event.getOldPresence().getStatus().equals(StatusType.OFFLINE) + && !event.getNewPresence().getStatus().equals(StatusType.OFFLINE) + && event.getUser().getRolesForGuild(DiscordPlugin.devServer).stream() + .anyMatch(r -> r.getLongID() == devrole.getLongID()) + && DiscordPlugin.devServer.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!", + new EmbedBuilder() + .withImage( + "https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png") + .build()); + lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime()); + } + } + }, (IListener) event -> { + Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { + if (event.getRole().isDeleted() || !DiscordPlugin.plugin.isGameRole(event.getRole())) + return; //Deleted or not a game role + DiscordPlugin.GameRoles.add(event.getRole().getName()); + DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Added " + event.getRole().getName() + " as game role. If you don't want this, change the role's color from the default."); + }, 100); + }, (IListener) event -> { + if (DiscordPlugin.GameRoles.remove(event.getRole().getName())) + DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Removed " + event.getRole().getName() + " as a game role."); + }, (IListener) event -> { //Role update event + if (!DiscordPlugin.plugin.isGameRole(event.getNewRole())) { + if (DiscordPlugin.GameRoles.remove(event.getOldRole().getName())) + DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Removed " + event.getOldRole().getName() + " as a game role because it's color changed."); + } else { + if (DiscordPlugin.GameRoles.contains(event.getOldRole().getName()) && event.getOldRole().getName().equals(event.getNewRole().getName())) + return; + boolean removed = DiscordPlugin.GameRoles.remove(event.getOldRole().getName()); //Regardless of whether it was a game role + DiscordPlugin.GameRoles.add(event.getNewRole().getName()); //Add it because it has no color + if (removed) + DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + "."); + else + DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Added " + event.getNewRole().getName() + " as game role because it has the default color."); + } + }}; + } + + private static boolean debug = false; + + public static void debug(String debug) { + if (CommonListeners.debug) //Debug + DPUtils.getLogger().info(debug); + } + + public static boolean debug() { + return debug = !debug; + } +} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java index 74979f8..44fe077 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java @@ -1,7 +1,7 @@ package buttondevteam.discordplugin.mcchat; +import buttondevteam.component.channel.Channel; import buttondevteam.discordplugin.DiscordConnectedPlayer; -import buttondevteam.lib.chat.Channel; import lombok.NonNull; import lombok.val; import sx.blah.discord.handle.obj.IChannel; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index c2ec714..8542eee 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -1,5 +1,7 @@ package buttondevteam.discordplugin.mcchat; +import buttondevteam.component.channel.Channel; +import buttondevteam.component.channel.ChatRoom; import buttondevteam.core.ComponentManager; import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlugin; @@ -10,9 +12,7 @@ import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.lib.TBMCChatEvent; import buttondevteam.lib.TBMCChatPreprocessEvent; import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.chat.Channel; import buttondevteam.lib.chat.ChatMessage; -import buttondevteam.lib.chat.ChatRoom; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.TBMCPlayer; import com.vdurmont.emoji.EmojiParser; @@ -72,14 +72,15 @@ public class MCChatListener implements Listener { e = se.getKey(); time = se.getValue(); - final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName) + "] " // + final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName().get()) + "] " // + ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().substring(0, 1) + "]") // + (DPUtils.sanitizeStringNoEscape(e.getSender() instanceof Player // ? ((Player) e.getSender()).getDisplayName() // : e.getSender().getName())); + val color = e.getChannel().Color().get(); final EmbedBuilder embed = new EmbedBuilder().withAuthorName(authorPlayer) - .withDescription(e.getMessage()).withColor(new Color(e.getChannel().color.getRed(), - e.getChannel().color.getGreen(), e.getChannel().color.getBlue())); + .withDescription(e.getMessage()).withColor(new Color(color.getRed(), + color.getGreen(), color.getBlue())); // embed.appendField("Channel", ((e.getSender() instanceof DiscordSenderBase ? "d|" : "") // + DiscordPlugin.sanitizeString(e.getChannel().DisplayName)), false); if (e.getSender() instanceof Player) @@ -313,7 +314,7 @@ public class MCChatListener implements Listener { .collect(Collectors.joining(", ")) + (user.getConnectedID(TBMCPlayer.class) == null ? "\nTo access your commands, first please connect your accounts, using /connect in " - + DiscordPlugin.botchannel.mention() + + DPUtils.botmention() + "\nThen y" : "\nY") + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!"); @@ -330,10 +331,10 @@ public class MCChatListener implements Listener { } else { int spi = cmdlowercased.indexOf(' '); final String topcmd = spi == -1 ? cmdlowercased : cmdlowercased.substring(0, spi); - Optional ch = Channel.getChannels().stream() + Optional ch = Channel.getChannels() .filter(c -> c.ID.equalsIgnoreCase(topcmd) - || (c.IDs != null && c.IDs.length > 0 - && Arrays.stream(c.IDs).anyMatch(id -> id.equalsIgnoreCase(topcmd)))).findAny(); + || (c.IDs().get().length > 0 + && Arrays.stream(c.IDs().get()).anyMatch(id -> id.equalsIgnoreCase(topcmd)))).findAny(); if (!ch.isPresent()) //TODO: What if talking in the public chat while we have it on a different one Bukkit.getScheduler().runTask(DiscordPlugin.plugin, //Commands need to be run sync () -> { //TODO: Better handling... @@ -366,7 +367,7 @@ public class MCChatListener implements Listener { } else channel.set(Channel.GlobalChat); dsender.sendMessage("You're now talking in: " - + DPUtils.sanitizeString(channel.get().DisplayName)); + + DPUtils.sanitizeString(channel.get().DisplayName().get())); } else { // Send single message final String msg = cmd.substring(spi + 1); val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(msg)).fromCommand(true); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java index 99c1740..877d415 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java @@ -59,7 +59,8 @@ public class MCChatPrivate { public static void logoutAll() { for (val entry : MCChatUtils.ConnectedSenders.entrySet()) for (val valueEntry : entry.getValue().entrySet()) - callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync + 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.ConnectedSenders.clear(); } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index 6826f6e..c246ddb 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -1,10 +1,10 @@ package buttondevteam.discordplugin.mcchat; +import buttondevteam.component.channel.Channel; import buttondevteam.core.ComponentManager; import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Channel; import io.netty.util.collection.LongObjectHashMap; import lombok.RequiredArgsConstructor; import lombok.experimental.var; diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java index 2f2ad21..29ed176 100755 --- a/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java @@ -1,43 +1,44 @@ -package buttondevteam.discordplugin.mccommands; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.commands.ConnectCommand; -import buttondevteam.discordplugin.mcchat.MCChatUtils; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.player.ChromaGamerBase; -import buttondevteam.lib.player.TBMCPlayer; -import buttondevteam.lib.player.TBMCPlayerBase; -import org.bukkit.entity.Player; - -@CommandClass(modOnly = false, path = "accept") -public class AcceptMCCommand extends DiscordMCCommandBase { - - @Override - public String[] GetHelpText(String alias) { - return new String[] { // - "§6---- Accept Discord connection ----", // - "Accept a pending connection between your Discord and Minecraft account.", // - "To start the connection process, do §b/connect §r in the #bot channel on Discord", // - "Usage: /" + alias + " accept" // - }; - } - - @Override - public boolean OnCommand(Player player, String alias, String[] args) { - String did = ConnectCommand.WaitingToConnect.get(player.getName()); - if (did == null) { - player.sendMessage("§cYou don't have a pending connection to Discord."); - return true; - } - DiscordPlayer dp = ChromaGamerBase.getUser(did, DiscordPlayer.class); - TBMCPlayer mcp = TBMCPlayerBase.getPlayer(player.getUniqueId(), TBMCPlayer.class); - dp.connectWith(mcp); - dp.save(); - mcp.save(); - ConnectCommand.WaitingToConnect.remove(player.getName()); - MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed - player.sendMessage("§bAccounts connected."); - return true; - } - -} +package buttondevteam.discordplugin.mccommands; + +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.DiscordPlayer; +import buttondevteam.discordplugin.commands.ConnectCommand; +import buttondevteam.discordplugin.mcchat.MCChatUtils; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.player.ChromaGamerBase; +import buttondevteam.lib.player.TBMCPlayer; +import buttondevteam.lib.player.TBMCPlayerBase; +import org.bukkit.entity.Player; + +@CommandClass(modOnly = false, path = "accept") +public class AcceptMCCommand extends DiscordMCCommandBase { + + @Override + public String[] GetHelpText(String alias) { + return new String[] { // + "§6---- Accept Discord connection ----", // + "Accept a pending connection between your Discord and Minecraft account.", // + "To start the connection process, do §b/connect §r in the " + DPUtils.botmention() + " channel on Discord", // + "Usage: /" + alias + " accept" // + }; + } + + @Override + public boolean OnCommand(Player player, String alias, String[] args) { + String did = ConnectCommand.WaitingToConnect.get(player.getName()); + if (did == null) { + player.sendMessage("§cYou don't have a pending connection to Discord."); + return true; + } + DiscordPlayer dp = ChromaGamerBase.getUser(did, DiscordPlayer.class); + TBMCPlayer mcp = TBMCPlayerBase.getPlayer(player.getUniqueId(), TBMCPlayer.class); + dp.connectWith(mcp); + dp.save(); + mcp.save(); + ConnectCommand.WaitingToConnect.remove(player.getName()); + MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed + player.sendMessage("§bAccounts connected."); + return true; + } + +} diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DeclineMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DeclineMCCommand.java index 831ad89..813d6b9 100755 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DeclineMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DeclineMCCommand.java @@ -1,31 +1,32 @@ -package buttondevteam.discordplugin.mccommands; - -import buttondevteam.discordplugin.commands.ConnectCommand; -import buttondevteam.lib.chat.CommandClass; -import org.bukkit.entity.Player; - -@CommandClass(modOnly = false, path = "decline") -public class DeclineMCCommand extends DiscordMCCommandBase { - - @Override - public String[] GetHelpText(String alias) { - return new String[] { // - "§6---- Decline Discord connection ----", // - "Decline a pending connection between your Discord and Minecraft account.", // - "To start the connection process, do §b/connect §r in the #bot channel on Discord", // - "Usage: /" + alias + " decline" // - }; - } - - @Override - public boolean OnCommand(Player player, String alias, String[] args) { - String did = ConnectCommand.WaitingToConnect.remove(player.getName()); - if (did == null) { - player.sendMessage("§cYou don't have a pending connection to Discord."); - return true; - } - player.sendMessage("§bPending connection declined."); - return true; - } - -} +package buttondevteam.discordplugin.mccommands; + +import buttondevteam.discordplugin.DPUtils; +import buttondevteam.discordplugin.commands.ConnectCommand; +import buttondevteam.lib.chat.CommandClass; +import org.bukkit.entity.Player; + +@CommandClass(modOnly = false, path = "decline") +public class DeclineMCCommand extends DiscordMCCommandBase { + + @Override + public String[] GetHelpText(String alias) { + return new String[] { // + "§6---- Decline Discord connection ----", // + "Decline a pending connection between your Discord and Minecraft account.", // + "To start the connection process, do §b/connect §r in the " + DPUtils.botmention() + " channel on Discord", // + "Usage: /" + alias + " decline" // + }; + } + + @Override + public boolean OnCommand(Player player, String alias, String[] args) { + String did = ConnectCommand.WaitingToConnect.remove(player.getName()); + if (did == null) { + player.sendMessage("§cYou don't have a pending connection to Discord."); + return true; + } + player.sendMessage("§bPending connection declined."); + return true; + } + +}