From 02f60c2162ae81ec39c1aa0e5ffba86c1f1e47c7 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 7 Nov 2019 00:20:57 +0100 Subject: [PATCH 1/7] Fix client ID race condition, attempt to fix URL escaping --- .../buttondevteam/discordplugin/DPUtils.java | 23 ++++++++++++++++--- .../mcchat/ChannelconCommand.java | 2 +- .../mcchat/MinecraftChatModule.java | 5 ++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index 84d2ced..ff81da4 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -15,11 +15,19 @@ import lombok.val; import reactor.core.publisher.Mono; import javax.annotation.Nullable; +import java.util.Comparator; +import java.util.Optional; +import java.util.TreeSet; +import java.util.function.Function; import java.util.logging.Logger; -import java.util.regex.Matcher; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; public final class DPUtils { + public static final Pattern URL_PATTERN = Pattern.compile("https?://\\S*"); + public static final Pattern FORMAT_PATTERN = Pattern.compile("[*_~]"); + public static EmbedCreateSpec embedWithHead(EmbedCreateSpec ecs, String displayname, String playername, String profileUrl) { return ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png"); } @@ -51,8 +59,17 @@ public final class DPUtils { } private static String escape(String message) { - return message.replaceAll("([*_~])", Matcher.quoteReplacement("\\") + "$1"); - } + //var ts = new TreeSet<>(); + var ts = new TreeSet(Comparator.comparingInt(a -> a[0])); //Compare the start, then check the end + var matcher = URL_PATTERN.matcher(message); + while (matcher.find()) + ts.add(new int[]{matcher.start(), matcher.end()}); + matcher = FORMAT_PATTERN.matcher(message); + Function aFunctionalInterface = result -> + Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start() + ? "\\\\" + result.group() : result.group(); + return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape + } //TODO: Java 11 method overload, not present in Java 8 public static Logger getLogger() { if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger() == null) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java index be1d284..38a0229 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java @@ -155,7 +155,7 @@ public class ChannelconCommand extends ICommand2DC { "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: " + "Invite link: " }; } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index b4586e1..4862c6a 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -104,13 +104,12 @@ public class MinecraftChatModule extends Component { return getConfig().getData("allowPrivateChat", true); } - String clientID; - @Override protected void enable() { if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono())) return; - DiscordPlugin.dc.getApplicationInfo().subscribe(info -> clientID = info.getId().asString()); + /*clientID = DiscordPlugin.dc.getApplicationInfo().blockOptional().map(info->info.getId().asString()) + .orElse("Unknown"); //Need to block because otherwise it may not be set in time*/ listener = new MCChatListener(this); TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin()); TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled From 19463963e3bdbe1d3dc28d20df59d2442f7684df Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 16 Nov 2019 01:52:49 +0100 Subject: [PATCH 2/7] Make channels default to 0, profile URL config Fix URL not-escaping Made the plugin only attempt to access channels that are not set to 0 #110 --- .../buttondevteam/discordplugin/DPUtils.java | 28 ++++++++++++++----- .../discordplugin/DiscordPlugin.java | 2 +- .../announcer/AnnouncerModule.java | 4 +-- .../exceptions/ExceptionListenerModule.java | 2 +- .../discordplugin/fun/FunModule.java | 2 +- .../discordplugin/mcchat/MCChatListener.java | 9 +++--- .../mcchat/MinecraftChatModule.java | 9 +++++- .../discordplugin/role/GameRoleModule.java | 2 +- 8 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index ff81da4..6f3b82c 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -18,9 +18,7 @@ import javax.annotation.Nullable; import java.util.Comparator; import java.util.Optional; import java.util.TreeSet; -import java.util.function.Function; import java.util.logging.Logger; -import java.util.regex.MatchResult; import java.util.regex.Pattern; public final class DPUtils { @@ -65,11 +63,19 @@ public final class DPUtils { while (matcher.find()) ts.add(new int[]{matcher.start(), matcher.end()}); matcher = FORMAT_PATTERN.matcher(message); - Function aFunctionalInterface = result -> + /*Function aFunctionalInterface = result -> Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start() ? "\\\\" + result.group() : result.group(); - return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape - } //TODO: Java 11 method overload, not present in Java 8 + return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, Optional.ofNullable(ts.floor(new int[]{matcher.start(), 0})) //Find a URL start <= our start + .map(a -> a[1]).orElse(-1) < matcher.start() //Check if URL end < our start + ? "\\\\" + matcher.group() : matcher.group()); + } + matcher.appendTail(sb); + return sb.toString(); + } public static Logger getLogger() { if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger() == null) @@ -77,8 +83,8 @@ public final class DPUtils { return DiscordPlugin.plugin.getLogger(); } - public static ReadOnlyConfigData> channelData(IHaveConfig config, String key, long defID) { - return config.getReadOnlyDataPrimDef(key, defID, id -> getMessageChannel(key, Snowflake.of((Long) id)), ch -> defID); //We can afford to search for the channel in the cache once (instead of using mainServer) + public static ReadOnlyConfigData> channelData(IHaveConfig config, String key) { + return config.getReadOnlyDataPrimDef(key, 0L, id -> getMessageChannel(key, Snowflake.of((Long) id)), ch -> 0L); //We can afford to search for the channel in the cache once (instead of using mainServer) } public static ReadOnlyConfigData> roleData(IHaveConfig config, String key, String defName) { @@ -169,7 +175,15 @@ public final class DPUtils { return "<#" + channelId.asString() + ">"; } + /** + * Gets a message channel for a config. Returns empty for ID 0. + * + * @param key The config key + * @param id The channel ID + * @return A message channel + */ public static Mono getMessageChannel(String key, Snowflake id) { + if (id.asLong() == 0L) return Mono.empty(); return DiscordPlugin.dc.getChannelById(id).onErrorResume(e -> { getLogger().warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage()); return Mono.empty(); diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 3c4bc68..11d9f29 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -201,7 +201,7 @@ public class DiscordPlugin extends ButtonPlugin { "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 its name.)" - + "\nYou can disable test mode in ThorpeCore config.")); + + "\nYou can disable test mode in ChromaCore config.")); Bukkit.getPluginManager().disablePlugin(this); } TBMCCoreAPI.SendUnsentExceptions(); diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java index 11250a8..5a28ece 100644 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java @@ -26,14 +26,14 @@ public class AnnouncerModule extends Component { * Channel to post new posts. */ public ReadOnlyConfigData> channel() { - return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); + return DPUtils.channelData(getConfig(), "channel"); } /** * Channel where distinguished (moderator) posts go. */ public ReadOnlyConfigData> modChannel() { - return DPUtils.channelData(getConfig(), "modChannel", 239519012529111040L); + return DPUtils.channelData(getConfig(), "modChannel"); } /** diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java index 17d029a..bfb3048 100755 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java @@ -85,7 +85,7 @@ public class ExceptionListenerModule extends Component implements } private ReadOnlyConfigData> channel() { - return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); + return DPUtils.channelData(getConfig(), "channel"); } private ConfigData> pingRole(Mono guild) { diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java index fa5e7d3..2ec9e2b 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java @@ -125,7 +125,7 @@ public class FunModule extends Component implements Listener { private ReadOnlyConfigData> fullHouseChannel() { - return DPUtils.channelData(getConfig(), "fullHouseChannel", 219626707458457603L); + return DPUtils.channelData(getConfig(), "fullHouseChannel"); } private static long lasttime = 0; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 52cc604..2487f30 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -84,13 +84,14 @@ public class MCChatListener implements Listener { final Consumer embed = ecs -> { ecs.setDescription(e.getMessage()).setColor(new Color(color.getRed(), color.getGreen(), color.getBlue())); + String url = module.profileURL().get(); if (e.getSender() instanceof Player) DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), - "https://tbmcplugins.github.io/profile.html?type=minecraft&id=" - + ((Player) e.getSender()).getUniqueId()); + url.length() > 0 ? url + "?type=minecraft&id=" + + ((Player) e.getSender()).getUniqueId() : null); else if (e.getSender() instanceof DiscordSenderBase) - ecs.setAuthor(authorPlayer, "https://tbmcplugins.github.io/profile.html?type=discord&id=" // TODO: Constant/method to get URLs like this - + ((DiscordSenderBase) e.getSender()).getUser().getId().asString(), + ecs.setAuthor(authorPlayer, url.length() > 0 ? url + "?type=discord&id=" + + ((DiscordSenderBase) e.getSender()).getUser().getId().asString() : null, ((DiscordSenderBase) e.getSender()).getUser().getAvatarUrl()); else DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), null); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index 4862c6a..eacf823 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -58,7 +58,7 @@ public class MinecraftChatModule extends Component { * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute */ public ReadOnlyConfigData> modlogChannel() { - return DPUtils.channelData(getConfig(), "modlogChannel", 283840717275791360L); + return DPUtils.channelData(getConfig(), "modlogChannel"); } /** @@ -104,6 +104,13 @@ public class MinecraftChatModule extends Component { return getConfig().getData("allowPrivateChat", true); } + /** + * If set, message authors appearing on Discord will link to this URL. A 'type' and 'id' parameter will be added with the user's platform (Discord, Minecraft, ...) and ID. + */ + public ConfigData profileURL() { + return getConfig().getData("profileURL", ""); + } + @Override protected void enable() { if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono())) diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java index babb47c..e4713b7 100644 --- a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java +++ b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java @@ -36,7 +36,7 @@ public class GameRoleModule extends Component { } private ReadOnlyConfigData> logChannel() { - return DPUtils.channelData(getConfig(), "logChannel", 239519012529111040L); + return DPUtils.channelData(getConfig(), "logChannel"); } public static void handleRoleEvent(RoleEvent roleEvent) { From 3a94b6191b3c54aedaccd036b37d15923502df67 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 28 Nov 2019 00:16:58 +0100 Subject: [PATCH 3/7] Update D4J and some bugfixes The update fixes the numerous errors about a missing status constant Remove test check (#114) Improvements and checking for admin permission (#115) It also checks for channel perms now --- pom.xml | 2 +- .../buttondevteam/discordplugin/DPUtils.java | 15 ++++ .../discordplugin/DiscordPlugin.java | 9 --- .../discordplugin/fun/FunModule.java | 4 +- .../listeners/CommandListener.java | 2 +- .../mcchat/ChannelconCommand.java | 74 +++++++++++-------- .../discordplugin/mcchat/MCChatCommand.java | 4 +- 7 files changed, 65 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index 65d6d72..40f2734 100755 --- a/pom.xml +++ b/pom.xml @@ -183,7 +183,7 @@ com.discord4j discord4j-core - 3.0.10 + 3.0.11 diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index 6f3b82c..b0c0990 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -157,12 +157,27 @@ public final class DPUtils { return false; } + /** + * Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object. + * + * @param original The original message to reply to + * @param channel The channel to send the message in, defaults to the original + * @param message The message to send + * @return A mono to send the message + */ public static Mono reply(Message original, @Nullable MessageChannel channel, String message) { Mono ch; if (channel == null) ch = original.getChannel(); else ch = Mono.just(channel); + return reply(original, ch, message); + } + + /** + * @see #reply(Message, MessageChannel, String) + */ + public static Mono reply(Message original, Mono ch, String message) { return ch.flatMap(chan -> chan.createMessage((original.getAuthor().isPresent() ? original.getAuthor().get().getMention() + ", " : "") + message)); } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 11d9f29..08225ff 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -44,7 +44,6 @@ import java.awt.*; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -196,14 +195,6 @@ public class DiscordPlugin extends ButtonPlugin { getConfig().set("serverup", true); saveConfig(); - if (TBMCCoreAPI.IsTestServer() && !Objects.requireNonNull(dc.getSelf().block()).getUsername().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 its name.)" - + "\nYou can disable test mode in ChromaCore config.")); - Bukkit.getPluginManager().disablePlugin(this); - } TBMCCoreAPI.SendUnsentExceptions(); TBMCCoreAPI.SendUnsentDebugMessages(); diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java index 2ec9e2b..46e98c5 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java @@ -96,7 +96,7 @@ public class FunModule extends Component implements Listener { } if (msglowercased.equals("list") && Bukkit.getOnlinePlayers().size() == lastlistp && ListC++ > 2) // Lowered already { - DPUtils.reply(message, null, "Stop it. You know the answer.").subscribe(); + DPUtils.reply(message, Mono.empty(), "stop it. You know the answer.").subscribe(); lastlist = 0; lastlistp = (short) Bukkit.getOnlinePlayers().size(); return true; //Handled @@ -108,7 +108,7 @@ public class FunModule extends Component implements Listener { if (usableServerReadyStrings.size() == 0) fm.createUsableServerReadyStrings(); next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size())); - DPUtils.reply(message, null, fm.serverReadyAnswers().get().get(next)).subscribe(); + DPUtils.reply(message, Mono.empty(), fm.serverReadyAnswers().get().get(next)).subscribe(); return false; //Still process it as a command/mcchat if needed } return false; diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java index 45aa261..1892e99 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.java @@ -62,7 +62,7 @@ public class CommandListener { try { timings.printElapsed("F"); if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString)) - return DPUtils.reply(message, channel, "Unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.\n" + cmdwithargsString) + return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.\n" + cmdwithargsString) .map(m -> false); } catch (Exception e) { TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java index 38a0229..e8307f3 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java @@ -9,12 +9,17 @@ import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.player.TBMCPlayer; +import discord4j.core.object.entity.GuildChannel; import discord4j.core.object.entity.Message; +import discord4j.core.object.entity.MessageChannel; +import discord4j.core.object.entity.User; import discord4j.core.object.util.Permission; import lombok.RequiredArgsConstructor; import lombok.val; import org.bukkit.Bukkit; +import reactor.core.publisher.Mono; +import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; @@ -22,6 +27,7 @@ import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Collectors; +@SuppressWarnings("SimplifyOptionalCallChains") //Java 11 @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.", // @@ -36,28 +42,29 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class ChannelconCommand extends ICommand2DC { private final MinecraftChatModule module; + @Command2.Subcommand public boolean remove(Command2DCSender sender) { val message = sender.getMessage(); - if (checkPerms(message)) return true; + if (checkPerms(message, null)) return true; if (MCChatCustom.removeCustomChat(message.getChannelId())) - DPUtils.reply(message, null, "channel connection removed.").subscribe(); + DPUtils.reply(message, Mono.empty(), "channel connection removed.").subscribe(); else - DPUtils.reply(message, null, "this channel isn't connected.").subscribe(); + DPUtils.reply(message, Mono.empty(), "this channel isn't connected.").subscribe(); return true; } @Command2.Subcommand public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) { val message = sender.getMessage(); - if (checkPerms(message)) return true; + if (checkPerms(message, null)) return true; val cc = MCChatCustom.getCustomChat(message.getChannelId()); 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) { - DPUtils.reply(message, null, "toggles:\n" + togglesString.get()).subscribe(); + DPUtils.reply(message, Mono.empty(), "toggles:\n" + togglesString.get()).subscribe(); return true; } String arg = toggle.toUpperCase(); @@ -65,7 +72,7 @@ public class ChannelconCommand extends ICommand2DC { if (!b.isPresent()) { val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg); if (bt == null) { - DPUtils.reply(message, null, "cannot find toggle. Toggles:\n" + togglesString.get()).subscribe(); + DPUtils.reply(message, Mono.empty(), "cannot find toggle. Toggles:\n" + togglesString.get()).subscribe(); return true; } final boolean add; @@ -83,7 +90,7 @@ public class ChannelconCommand extends ICommand2DC { //1 1 | 0 // XOR cc.toggles ^= b.get().flag; - DPUtils.reply(message, null, "'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")).subscribe(); + DPUtils.reply(message, Mono.empty(), "'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")).subscribe(); return true; } @@ -94,12 +101,13 @@ public class ChannelconCommand extends ICommand2DC { sender.sendMessage("channel connection is not allowed on this Minecraft server."); return true; } - if (checkPerms(message)) return true; + val channel = message.getChannel().block(); + if (checkPerms(message, channel)) return true; if (MCChatCustom.hasCustomChat(message.getChannelId())) 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) - DPUtils.reply(message, null, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe(); + DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe(); return true; } if (!message.getAuthor().isPresent()) return true; @@ -107,19 +115,18 @@ public class ChannelconCommand extends ICommand2DC { val dp = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class); val chp = dp.getAs(TBMCPlayer.class); if (chp == null) { - DPUtils.reply(message, null, "you need to connect your Minecraft account. On our server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect ").subscribe(); + DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect ").subscribe(); return true; } - val channel = message.getChannel().block(); DiscordConnectedPlayer dcp = DiscordConnectedPlayer.create(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module); //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 - DPUtils.reply(message, null, "sorry, you cannot use that Minecraft channel.").subscribe(); + DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe(); return true; } if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well - DPUtils.reply(message, null, "chat rooms are not supported yet.").subscribe(); + DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe(); return true; } /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { @@ -128,16 +135,23 @@ public class ChannelconCommand extends ICommand2DC { }*/ //TODO: "Channel admins" that can connect channels? MCChatCustom.addCustomChat(channel, groupid, chan.get(), author, dcp, 0, new HashSet<>()); if (chan.get() instanceof ChatRoom) - DPUtils.reply(message, null, "alright, connection made to the room!").subscribe(); + DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe(); else - DPUtils.reply(message, null, "alright, connection made to group `" + groupid + "`!").subscribe(); + DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe(); return true; } @SuppressWarnings("ConstantConditions") - private boolean checkPerms(Message message) { - if (!message.getAuthorAsMember().block().getBasePermissions().block().contains(Permission.MANAGE_CHANNELS)) { - DPUtils.reply(message, null, "you need to have manage permissions for this channel!").subscribe(); + private boolean checkPerms(Message message, @Nullable MessageChannel channel) { + if (channel == null) + channel = message.getChannel().block(); + if (!(channel instanceof GuildChannel)) { + DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe(); + return true; + } + var perms = ((GuildChannel) channel).getEffectivePermissions(message.getAuthor().map(User::getId).get()).block(); + if (!perms.contains(Permission.ADMINISTRATOR) && !perms.contains(Permission.MANAGE_CHANNELS)) { + DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe(); return true; } return false; @@ -145,17 +159,17 @@ public class ChannelconCommand extends ICommand2DC { @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.", // + 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: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf().block()).getMention() + " 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: " - }; - } + "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/mcchat/MCChatCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java index f068a12..4319cf9 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java @@ -33,13 +33,13 @@ public class MCChatCommand extends ICommand2DC { val channel = message.getChannel().block(); @SuppressWarnings("OptionalGetWithoutIsPresent") val author = message.getAuthor().get(); if (!(channel instanceof PrivateChannel)) { - DPUtils.reply(message, null, "this command can only be issued in a direct message with the bot.").subscribe(); + DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe(); return true; } try (final DiscordPlayer user = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class)) { boolean mcchat = !user.isMinecraftChatEnabled(); MCChatPrivate.privateMCChat(channel, mcchat, author, user); - DPUtils.reply(message, null, "Minecraft chat " + (mcchat // + DPUtils.reply(message, channel, "Minecraft chat " + (mcchat // ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." // : "disabled.")).subscribe(); } catch (Exception e) { From b481bb0aa998537adfc93a92149a5b73607af8af Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 27 Dec 2019 21:14:52 +0100 Subject: [PATCH 4/7] Fixed join msgs, vanished players in desc. and others Update D4J Fixed join messages appearing when they shouldn't (#119) Only showing players who can see the channel (#91) Fixed vanished players appearing in the channel descriptions (#120) --- pom.xml | 2 +- .../discordplugin/mcchat/MCChatUtils.java | 21 ++++++++++++++-- .../discordplugin/mcchat/MCListener.java | 24 ++++++++++--------- src/main/resources/plugin.yml | 4 +++- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 40f2734..299d605 100755 --- a/pom.xml +++ b/pom.xml @@ -183,7 +183,7 @@ com.discord4j discord4j-core - 3.0.11 + 3.0.12 diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index 6c085db..6b1c61d 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.ComponentManager; +import buttondevteam.core.MainPlugin; import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.lib.TBMCCoreAPI; @@ -13,6 +14,7 @@ import lombok.RequiredArgsConstructor; import lombok.val; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; @@ -30,6 +32,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; @@ -81,13 +84,27 @@ public class MCChatUtils { String[] s = topic.split("\\n----\\n"); if (s.length < 3) return; - s[0] = Bukkit.getOnlinePlayers().size() + " player" + (Bukkit.getOnlinePlayers().size() != 1 ? "s" : "") - + " online"; + String gid; + if (lmd instanceof MCChatCustom.CustomLMD) + gid = ((MCChatCustom.CustomLMD) lmd).groupID; + else //If we're not using a custom chat then it's either can ("everyone") or can't (null) see at most + gid = buttondevteam.core.component.channel.Channel.GROUP_EVERYONE; // (Though it's a public chat then rn) + AtomicInteger C = new AtomicInteger(); s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream() + .filter(p -> gid.equals(lmd.mcchannel.getGroupID(p))) //If they can see it + .filter(MCChatUtils::checkEssentials) + .filter(p -> C.incrementAndGet() > 0) //Always true .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", ")); + s[0] = C + " player" + (C.get() != 1 ? "s" : "") + " online"; ((TextChannel) lmd.channel).edit(tce -> tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe(); //Don't wait } + private static boolean checkEssentials(Player p) { + var ess = MainPlugin.ess; + if (ess == null) return true; + return !ess.getUser(p).isHidden(); + } + public static T addSender(HashMap> senders, User user, T sender) { return addSender(senders, user.getId().asString(), sender); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index c3ad14a..144304d 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -3,7 +3,9 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.discordplugin.*; import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.player.*; +import buttondevteam.lib.player.TBMCPlayer; +import buttondevteam.lib.player.TBMCPlayerBase; +import buttondevteam.lib.player.TBMCYEEHAWEvent; import com.earth2me.essentials.CommandSource; import discord4j.core.object.entity.Role; import discord4j.core.object.util.Snowflake; @@ -18,9 +20,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerCommandSendEvent; -import org.bukkit.event.player.PlayerKickEvent; -import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.*; import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.server.BroadcastMessageEvent; import org.bukkit.event.server.TabCompleteEvent; @@ -45,12 +45,12 @@ class MCListener implements Listener { } @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerJoin(TBMCPlayerJoinEvent e) { + public void onPlayerJoin(PlayerJoinEvent e) { if (e.getPlayer() instanceof DiscordConnectedPlayer) return; // Don't show the joined message for the fake player Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> { final Player p = e.getPlayer(); - DiscordPlayer dp = e.GetPlayer().getAs(DiscordPlayer.class); + DiscordPlayer dp = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class).getAs(DiscordPlayer.class); if (dp != null) { DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).flatMap(user -> user.getPrivateChannel().flatMap(chan -> module.chatChannelMono().flatMap(cc -> { MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), @@ -60,14 +60,15 @@ class MCListener implements Listener { return Mono.empty(); }))).subscribe(); } - final String message = e.GetPlayer().PlayerName().get() + " joined the game"; - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); + final String message = e.getJoinMessage(); + if (message != null && message.trim().length() > 0) + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); ChromaBot.getInstance().updatePlayerList(); }); } @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerLeave(TBMCPlayerQuitEvent e) { + public void onPlayerLeave(PlayerQuitEvent e) { if (e.getPlayer() instanceof DiscordConnectedPlayer) return; // Only care about real users MCChatUtils.OnlineSenders.entrySet() @@ -78,8 +79,9 @@ class MCListener implements Listener { .ifPresent(MCChatUtils::callLoginEvents)); Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, ChromaBot.getInstance()::updatePlayerList, 5); - final String message = e.GetPlayer().PlayerName().get() + " left the game"; - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); + final String message = e.getQuitMessage(); + if (message != null && message.trim().length() > 0) + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); } @EventHandler(priority = EventPriority.HIGHEST) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e045c2b..f7ffcf1 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,8 +1,10 @@ name: Chroma-Discord main: buttondevteam.discordplugin.DiscordPlugin -version: 1.0 +version: '1.0' author: NorbiPeti depend: [ChromaCore] +softdepend: + - Essentials commands: discord: website: 'https://github.com/TBMCPlugins/DiscordPlugin' From 703f1f8cd5110850b2974be45d9570faf7a63c57 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 27 Dec 2019 23:45:49 +0100 Subject: [PATCH 5/7] Finished some of the half-completed issues and others Processing custom emotes (#48) Made role listing fancier (#80) Trying to reload config before reset (#113) Allowing /discord reset if the login fails, clarified how to get a token (#111) --- .../buttondevteam/discordplugin/DiscordPlugin.java | 12 +++++++----- .../discordplugin/mcchat/MCChatListener.java | 2 ++ .../discordplugin/mccommands/DiscordMCCommand.java | 4 ++++ .../discordplugin/role/RoleCommand.java | 14 ++++++++++++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 08225ff..81bb8bd 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -100,6 +100,7 @@ public class DiscordPlugin extends ButtonPlugin { getLogger().info("Initializing..."); plugin = this; manager = new Command2DC(); + getCommand2MC().registerCommand(new DiscordMCCommand()); //Register so that the reset command works String token; File tokenFile = new File("TBMC", "Token.txt"); if (tokenFile.exists()) //Legacy support @@ -113,8 +114,9 @@ public class DiscordPlugin extends ButtonPlugin { conf.set("token", "Token goes here"); conf.save(privateFile); - getLogger().severe("Token not found! Set it in private.yml"); - Bukkit.getPluginManager().disablePlugin(this); + getLogger().severe("Token not found! Please set it in private.yml then do /discord reset"); + getLogger().severe("You need to have a bot account to use with your server."); + getLogger().severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token."); return; } } @@ -132,8 +134,8 @@ public class DiscordPlugin extends ButtonPlugin { //dc.getEventDispatcher().on(DisconnectEvent.class); dc.login().subscribe(); } catch (Exception e) { - e.printStackTrace(); - Bukkit.getPluginManager().disablePlugin(this); + TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e); + getLogger().severe("You may be able to reset the plugin using /discord reset"); } } @@ -143,10 +145,10 @@ public class DiscordPlugin extends ButtonPlugin { try { if (mainServer != null) { //This is not the first ready event getLogger().info("Ready event already handled"); //TODO: It should probably handle disconnections + dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe(); //Update from the initial presence return; } mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards - getCommand2MC().registerCommand(new DiscordMCCommand()); //Register so that the reset command works if (mainServer == null) { if (event.size() == 0) { getLogger().severe("Main server not found! Invite the bot and do /discord reset"); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 2487f30..401d6e3 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -293,6 +293,8 @@ public class MCChatListener implements Listener { dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE); //Converts emoji to text- TODO: Add option to disable (resource pack?) dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:"); //Convert to Discord's format so it still shows up + dmessage = dmessage.replaceAll("", ":$1:"); //We don't need info about the custom emojis, just display their text + Function getChatMessage = msg -> // msg + (event.getMessage().getAttachments().size() > 0 ? "\n" + event.getMessage() .getAttachments().stream().map(Attachment::getUrl).collect(Collectors.joining("\n")) diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java index 29eb5ce..1f2d7a7 100644 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java @@ -73,6 +73,10 @@ public class DiscordMCCommand extends ICommand2MC { }) public void reset(CommandSender sender) { Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> { + if (!DiscordPlugin.plugin.tryReloadConfig()) { + sender.sendMessage("§cFailed to reload config so not resetting. Check the console."); + return; + } resetting = true; //Turned off after sending enable message (ReadyEvent) sender.sendMessage("§bDisabling DiscordPlugin..."); Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin); diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java index d47007b..2dab969 100755 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java @@ -11,7 +11,6 @@ import lombok.val; import reactor.core.publisher.Mono; import java.util.List; -import java.util.stream.Collectors; @CommandClass public class RoleCommand extends ICommand2DC { @@ -62,7 +61,18 @@ public class RoleCommand extends ICommand2DC { @Command2.Subcommand public void list(Command2DCSender sender) { - sender.sendMessage("list of roles:\n" + grm.GameRoles.stream().sorted().collect(Collectors.joining("\n"))); + var sb = new StringBuilder(); + boolean b = false; + for (String role : (Iterable) grm.GameRoles.stream().sorted()::iterator) { + sb.append(role); + if (!b) + for (int j = 0; j < Math.min(0, 20 - role.length()); j++) + sb.append(" "); + else + sb.append("\n"); + b = !b; + } + sender.sendMessage("list of roles:\n```\n" + sb + "```"); } private Role checkAndGetRole(Command2DCSender sender, String rolename) { From bdb7381ab4de5d1401e0a0dbd956fbbf44cb8272 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 18 Jan 2020 03:54:17 +0100 Subject: [PATCH 6/7] Finished some issues Fixed join messages appearing in addition to custom ones (#119) For real this time Not saying the game role color is the default one (#118) Fixed role listing (#80) --- .../discordplugin/mcchat/MCListener.java | 4 ++-- .../discordplugin/role/GameRoleModule.java | 11 +++++++++-- .../buttondevteam/discordplugin/role/RoleCommand.java | 4 +++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index 144304d..50843e6 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -44,7 +44,7 @@ class MCListener implements Listener { .ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false)); } - @EventHandler(priority = EventPriority.LOWEST) + @EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent e) { if (e.getPlayer() instanceof DiscordConnectedPlayer) return; // Don't show the joined message for the fake player @@ -67,7 +67,7 @@ class MCListener implements Listener { }); } - @EventHandler(priority = EventPriority.HIGHEST) + @EventHandler(priority = EventPriority.MONITOR) public void onPlayerLeave(PlayerQuitEvent e) { if (e.getPlayer() instanceof DiscordConnectedPlayer) return; // Only care about real users diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java index e4713b7..d666a13 100644 --- a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java +++ b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java @@ -21,6 +21,10 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +/** + * Automatically collects roles with a certain color (the second to last in the upper row - #95a5a6). + * Users can add these roles to themselves using the /role Discord command. + */ public class GameRoleModule extends Component { public List GameRoles; @@ -35,6 +39,9 @@ public class GameRoleModule extends Component { } + /** + * The channel where the bot logs when it detects a role change that results in a new game role or one being removed. + */ private ReadOnlyConfigData> logChannel() { return DPUtils.channelData(getConfig(), "logChannel"); } @@ -52,7 +59,7 @@ public class GameRoleModule extends Component { return Mono.empty(); //Deleted or not a game role GameRoles.add(role.getName()); if (logChannel != null) - return logChannel.flatMap(ch -> ch.createMessage("Added " + role.getName() + " as game role. If you don't want this, change the role's color from the default.")); + return logChannel.flatMap(ch -> ch.createMessage("Added " + role.getName() + " as game role. If you don't want this, change the role's color from the game role color.")); return Mono.empty(); }).subscribe(); }, 100); @@ -81,7 +88,7 @@ public class GameRoleModule extends Component { if (removed) return logChannel.flatMap(ch -> ch.createMessage("Changed game role from " + or.getName() + " to " + event.getCurrent().getName() + ".")); else - return logChannel.flatMap(ch -> ch.createMessage("Added " + event.getCurrent().getName() + " as game role because it has the default color.")); + return logChannel.flatMap(ch -> ch.createMessage("Added " + event.getCurrent().getName() + " as game role because it has the color of one.")); } } return Mono.empty(); diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java index 2dab969..d484ef2 100755 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java @@ -66,12 +66,14 @@ public class RoleCommand extends ICommand2DC { for (String role : (Iterable) grm.GameRoles.stream().sorted()::iterator) { sb.append(role); if (!b) - for (int j = 0; j < Math.min(0, 20 - role.length()); j++) + for (int j = 0; j < Math.max(1, 20 - role.length()); j++) sb.append(" "); else sb.append("\n"); b = !b; } + if (sb.charAt(sb.length() - 1) != '\n') + sb.append('\n'); sender.sendMessage("list of roles:\n```\n" + sb + "```"); } From de07503bc3c7ff313a8ec00a32de93361c794529 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 1 Feb 2020 19:10:13 +0100 Subject: [PATCH 7/7] Documentation, split messages that are too big #122 Removed some default values Disallowing MC commands that could error when not loaded (#121) Other fixes --- .../buttondevteam/discordplugin/DPUtils.java | 11 ++++-- .../discordplugin/DiscordPlugin.java | 16 ++++++-- .../announcer/AnnouncerModule.java | 23 ++++++----- .../GeneralEventBroadcasterModule.java | 4 ++ .../commands/Command2DCSender.java | 6 +++ .../exceptions/ExceptionListenerModule.java | 9 +++++ .../discordplugin/fun/FunModule.java | 39 +++++++++++-------- .../discordplugin/mcchat/MCChatListener.java | 3 +- .../mcchat/MinecraftChatModule.java | 11 ++---- .../mccommands/DiscordMCCommand.java | 11 ++++++ 10 files changed, 91 insertions(+), 42 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index b0c0990..d118672 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -96,13 +96,16 @@ public final class DPUtils { */ public static ReadOnlyConfigData> roleData(IHaveConfig config, String key, String defName, Mono guild) { return config.getReadOnlyDataPrimDef(key, defName, name -> { - if (!(name instanceof String)) return Mono.empty(); - return guild.flatMapMany(Guild::getRoles).filter(r -> r.getName().equals(name)).next(); + if (!(name instanceof String) || ((String) name).length() == 0) return Mono.empty(); + return guild.flatMapMany(Guild::getRoles).filter(r -> r.getName().equals(name)).onErrorResume(e -> { + getLogger().warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage()); + return Mono.empty(); + }).next(); }, r -> defName); } - public static ConfigData snowflakeData(IHaveConfig config, String key, long defID) { - return config.getDataPrimDef(key, defID, id -> Snowflake.of((long) id), Snowflake::asLong); + public static ReadOnlyConfigData snowflakeData(IHaveConfig config, String key, long defID) { + return config.getReadOnlyDataPrimDef(key, defID, id -> Snowflake.of((long) id), Snowflake::asLong); } /** diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java index 81bb8bd..a38389a 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.java @@ -55,6 +55,9 @@ public class DiscordPlugin extends ButtonPlugin { @Getter private Command2DC manager; + /** + * The prefix to use with Discord commands like /role. It only works in the bot channel. + */ private ConfigData prefix() { return getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString); } @@ -64,6 +67,9 @@ public class DiscordPlugin extends ButtonPlugin { return plugin.prefix().get(); } + /** + * The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to. + */ private ConfigData> mainServer() { return getIConfig().getDataPrimDef("mainServer", 0L, id -> { @@ -76,12 +82,16 @@ public class DiscordPlugin extends ButtonPlugin { g -> g.map(gg -> gg.getId().asLong()).orElse(0L)); } + /** + * The (bot) channel to use for Discord commands like /role. + */ public ConfigData commandChannel() { - return DPUtils.snowflakeData(getIConfig(), "commandChannel", 239519012529111040L); + return DPUtils.snowflakeData(getIConfig(), "commandChannel", 0L); } /** - * If the role doesn't exist, then it will only allow for the owner. + * The role that allows using mod-only Discord commands. + * If empty (''), then it will only allow for the owner. */ public ConfigData> modRole() { return DPUtils.roleData(getIConfig(), "modRole", "Moderator"); @@ -164,7 +174,7 @@ public class DiscordPlugin extends ButtonPlugin { } SafeMode = false; DPUtils.disableIfConfigErrorRes(null, commandChannel(), DPUtils.getMessageChannel(commandChannel())); - DPUtils.disableIfConfigError(null, modRole()); //Won't disable, just prints the warning here + //Won't disable, just prints the warning here Component.registerComponent(this, new GeneralEventBroadcasterModule()); Component.registerComponent(this, new MinecraftChatModule()); diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java index 5a28ece..2b68de0 100644 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.architecture.ComponentMetadata; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ReadOnlyConfigData; import buttondevteam.lib.player.ChromaGamerBase; @@ -15,12 +16,13 @@ import com.google.gson.JsonParser; import discord4j.core.object.entity.Message; import discord4j.core.object.entity.MessageChannel; import lombok.val; -import org.bukkit.configuration.file.YamlConfiguration; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.io.File; - +/** + * Posts new posts from Reddit to the specified channel(s). It will pin the regular posts (not the mod posts). + */ +@ComponentMetadata(enabledByDefault = false) public class AnnouncerModule extends Component { /** * Channel to post new posts. @@ -51,7 +53,13 @@ public class AnnouncerModule extends Component { return getConfig().getData("lastSeenTime", 0L); } - private static final String SubredditURL = "https://www.reddit.com/r/ChromaGamers"; + /** + * The subreddit to pull the posts from + */ + private ConfigData subredditURL() { + return getConfig().getData("subredditURL", "https://www.reddit.com/r/ChromaGamers"); + } + private static boolean stop = false; @Override @@ -62,11 +70,6 @@ public class AnnouncerModule extends Component { if (keepPinned == 0) return; Flux msgs = channel().get().flatMapMany(MessageChannel::getPinnedMessages); msgs.subscribe(Message::unpin); - val yc = YamlConfiguration.loadConfiguration(new File("plugins/DiscordPlugin", "config.yml")); //Name change - if (lastAnnouncementTime().get() == 0) //Load old data - lastAnnouncementTime().set(yc.getLong("lastannouncementtime")); - if (lastSeenTime().get() == 0) - lastSeenTime().set(yc.getLong("lastseentime")); new Thread(this::AnnouncementGetterThreadMethod).start(); } @@ -82,7 +85,7 @@ public class AnnouncerModule extends Component { Thread.sleep(10000); continue; } - String body = TBMCCoreAPI.DownloadString(SubredditURL + "/new/.json?limit=10"); + String body = TBMCCoreAPI.DownloadString(subredditURL().get() + "/new/.json?limit=10"); JsonArray json = new JsonParser().parse(body).getAsJsonObject().get("data").getAsJsonObject() .get("children").getAsJsonArray(); StringBuilder msgsb = new StringBuilder(); diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java index 44c2d79..5a0317e 100644 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java @@ -6,6 +6,10 @@ import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; import lombok.Getter; +/** + * Uses a bit of a hacky method of getting all broadcasted messages, including advancements and any other message that's for everyone. + * If this component is enabled then these messages will show up on Discord. + */ public class GeneralEventBroadcasterModule extends Component { private static @Getter boolean hooked = false; diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java index 8b705fd..ab22e37 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java @@ -3,6 +3,7 @@ package buttondevteam.discordplugin.commands; import buttondevteam.discordplugin.DPUtils; import buttondevteam.lib.chat.Command2Sender; import discord4j.core.object.entity.Message; +import discord4j.core.object.entity.User; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.val; @@ -30,4 +31,9 @@ public class Command2DCSender implements Command2Sender { public void sendMessage(String[] message) { sendMessage(String.join("\n", message)); } + + @Override + public String getName() { + return message.getAuthor().map(User::getUsername).orElse("Discord"); + } } diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java index bfb3048..6263e85 100755 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java @@ -23,6 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +/** + * Listens for errors from the Chroma plugins and posts them to Discord, ignoring repeating errors so it's not that spammy. + */ public class ExceptionListenerModule extends Component implements Listener { private List lastthrown = new ArrayList<>(); private List lastsourcemsg = new ArrayList<>(); @@ -84,10 +87,16 @@ public class ExceptionListenerModule extends Component implements return Mono.empty(); } + /** + * The channel to post the errors to. + */ private ReadOnlyConfigData> channel() { return DPUtils.channelData(getConfig(), "channel"); } + /** + * The role to ping if an error occurs. Set to empty ('') to disable. + */ private ConfigData> pingRole(Mono guild) { return DPUtils.roleData(getConfig(), "pingRole", "Coder", guild); } diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java index 46e98c5..189a026 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java @@ -26,23 +26,24 @@ import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; /** + * All kinds of random things. * The YEEHAW event uses an emoji named :YEEHAW: if available */ 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 - "In about 3 seconds", // Nicolai - "After we finish 8 plugins", // Ali - "Tomorrow.", // Ali - "After one tiiiny feature", // Ali - "Next commit", // Ali - "After we finish strangling Towny", // Ali - "When we kill every *fucking* bug", // Ali - "Once the server stops screaming.", // Ali - "After HL3 comes out", // Ali - "Next time you ask", // Ali - "When will *you* be open?" // Ali + 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 + "in about 3 seconds", // Nicolai + "after we finish 8 plugins", // Ali + "tomorrow.", // Ali + "after one tiiiny feature", // Ali + "next commit", // Ali + "after we finish strangling Towny", // Ali + "when we kill every *fucking* bug", // Ali + "once the server stops screaming.", // Ali + "after HL3 comes out", // Ali + "next time you ask", // Ali + "when will *you* be open?" // Ali }; /** @@ -52,14 +53,14 @@ public class FunModule extends Component implements Listener { return getConfig().getData("serverReady", () -> new String[]{"when will the server be open", "when will the server be ready", "when will the server be done", "when will the server be complete", "when will the server be finished", "when's the server ready", "when's the server open", - "Vhen vill ze server be open?"}); + "vhen vill ze server be open?"}); } /** * Answers for a recognized question. Selected randomly. */ private ConfigData> serverReadyAnswers() { - return getConfig().getData("serverReadyAnswers", () -> Lists.newArrayList(serverReadyStrings)); //TODO: Test + return getConfig().getData("serverReadyAnswers", () -> Lists.newArrayList(serverReadyStrings)); } private static final Random serverReadyRandom = new Random(); @@ -119,11 +120,17 @@ public class FunModule extends Component implements Listener { ListC = 0; } + /** + * If all of the people who have this role are online, the bot will post a full house. + */ private ConfigData> fullHouseDevRole(Mono guild) { return DPUtils.roleData(getConfig(), "fullHouseDevRole", "Developer", guild); } + /** + * The channel to post the full house to. + */ private ReadOnlyConfigData> fullHouseChannel() { return DPUtils.channelData(getConfig(), "fullHouseChannel"); } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 401d6e3..202fd93 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -102,7 +102,8 @@ public class MCChatListener implements Listener { if (lastmsgdata.message == null || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().map(Embed.Author::getName).orElse(null)) || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 - || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) { + || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID) + || lastmsgdata.content.length() + e.getMessage().length() + 1 > 2048) { lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block(); lastmsgdata.time = nanoTime; lastmsgdata.mcchannel = e.getChannel(); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index eacf823..1018ba8 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -28,12 +28,7 @@ import java.util.stream.Collectors; * 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; - And now ButtonProcessor didn't look beyond this - return instead of continue... - }*/ + private @Getter MCChatListener 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! @@ -46,8 +41,8 @@ public class MinecraftChatModule extends Component { /** * The channel to use as the public Minecraft chat - everything public gets broadcasted here */ - public ConfigData chatChannel() { - return DPUtils.snowflakeData(getConfig(), "chatChannel", 239519012529111040L); + public ReadOnlyConfigData chatChannel() { + return DPUtils.snowflakeData(getConfig(), "chatChannel", 0L); } public Mono chatChannelMono() { diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java index 1f2d7a7..61d9d6b 100644 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java @@ -27,6 +27,7 @@ import java.lang.reflect.Method; public class DiscordMCCommand extends ICommand2MC { @Command2.Subcommand public boolean accept(Player player) { + if (checkSafeMode(player)) return true; String did = ConnectCommand.WaitingToConnect.get(player.getName()); if (did == null) { player.sendMessage("§cYou don't have a pending connection to Discord."); @@ -45,6 +46,7 @@ public class DiscordMCCommand extends ICommand2MC { @Command2.Subcommand public boolean decline(Player player) { + if (checkSafeMode(player)) return true; String did = ConnectCommand.WaitingToConnect.remove(player.getName()); if (did == null) { player.sendMessage("§cYou don't have a pending connection to Discord."); @@ -101,6 +103,7 @@ public class DiscordMCCommand extends ICommand2MC { "Shows an invite link to the server" }) public void invite(CommandSender sender) { + if (checkSafeMode(sender)) return; String invi = DiscordPlugin.plugin.inviteLink().get(); if (invi.length() > 0) { sender.sendMessage("§bInvite link: " + invi); @@ -132,4 +135,12 @@ public class DiscordMCCommand extends ICommand2MC { return super.getHelpText(method, ann); } } + + private boolean checkSafeMode(CommandSender sender) { + if (DiscordPlugin.SafeMode) { + sender.sendMessage("§cThe plugin isn't initialized. Check console for details."); + return true; + } + return false; + } }