diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index fb832ba..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index fb7f4a8..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 7754215..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 6e1f504..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index b3e9cbd..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4bc4fc6..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/build.gradle b/build.gradle index d35b83d..0399ff8 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,9 @@ dependencies { implementation group: 'com.electronwill.night-config', name: 'toml', version: nightconfig_version implementation group: 'net.sf.jopt-simple', name: 'jopt-simple', version: jopt_version implementation group: 'com.google.guava', name: 'guava', version: guava_version - implementation group: 'com.google.code.gson', name: 'gson', version: gson_version + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jackson_version + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jackson_version + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jackson_version implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: log4j_bridge_version implementation group: 'ch.qos.logback', name: 'logback-classic', version: logback_version implementation group: 'com.mojang', name: 'brigadier', version: brigadier_version diff --git a/gradle.properties b/gradle.properties index 5124242..90fffd0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ jda_version=4.2.0_207 nightconfig_version=3.6.3 jopt_version=6.0-alpha-3 guava_version=29.0-jre -gson_version=2.8.6 +jackson_version=2.11.2 log4j_bridge_version=2.13.3 logback_version=1.3.0-alpha5 brigadier_version=1.0.17 diff --git a/src/main/java/sciwhiz12/janitor/BotConsole.java b/src/main/java/sciwhiz12/janitor/BotConsole.java index 4f2526a..ebb83c5 100644 --- a/src/main/java/sciwhiz12/janitor/BotConsole.java +++ b/src/main/java/sciwhiz12/janitor/BotConsole.java @@ -49,6 +49,11 @@ public class BotConsole { bot.getTranslations().loadTranslations(); break outer; } + case "messages": { + CONSOLE.info("Reloading messages"); + bot.getMessages().loadMessages(); + break outer; + } } } default: @@ -71,8 +76,7 @@ public class BotConsole { while (!scanner.hasNextLine()) { try { Thread.sleep(150); - } - catch (InterruptedException e) { + } catch (InterruptedException e) { CONSOLE.warn("Console thread is interrupted"); continue outer; } @@ -84,8 +88,7 @@ public class BotConsole { } CONSOLE.debug("Received command: {}", input); BotConsole.this.parseCommand(input); - } - catch (Exception e) { + } catch (Exception e) { CONSOLE.error("Error while running console thread", e); } } diff --git a/src/main/java/sciwhiz12/janitor/BotStartup.java b/src/main/java/sciwhiz12/janitor/BotStartup.java index 3dcfb0a..80a4563 100644 --- a/src/main/java/sciwhiz12/janitor/BotStartup.java +++ b/src/main/java/sciwhiz12/janitor/BotStartup.java @@ -1,5 +1,6 @@ package sciwhiz12.janitor; +import com.google.common.base.Preconditions; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; @@ -12,7 +13,6 @@ import sciwhiz12.janitor.config.BotOptions; import java.util.EnumSet; -import static com.google.common.base.Preconditions.checkArgument; import static sciwhiz12.janitor.Logging.JANITOR; public class BotStartup { @@ -21,7 +21,7 @@ public class BotStartup { BotOptions options = new BotOptions(args); BotConfig config = new BotConfig(options); - checkArgument(!config.getToken().isEmpty(), "Supply a client token through config or command line"); + Preconditions.checkArgument(!config.getToken().isEmpty(), "Supply a client token through config or command line"); JANITOR.info("Building bot instance and connecting to Discord..."); @@ -37,8 +37,7 @@ public class BotStartup { } }) .build(); - } - catch (Exception ex) { + } catch (Exception ex) { JANITOR.error("Error while building Discord connection", ex); } } diff --git a/src/main/java/sciwhiz12/janitor/JanitorBot.java b/src/main/java/sciwhiz12/janitor/JanitorBot.java index ecd8756..232e5fe 100644 --- a/src/main/java/sciwhiz12/janitor/JanitorBot.java +++ b/src/main/java/sciwhiz12/janitor/JanitorBot.java @@ -9,7 +9,9 @@ import net.dv8tion.jda.api.entities.User; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.config.BotConfig; import sciwhiz12.janitor.msg.Messages; -import sciwhiz12.janitor.msg.Translations; +import sciwhiz12.janitor.msg.TranslationMap; +import sciwhiz12.janitor.msg.emote.ReactionManager; +import sciwhiz12.janitor.msg.substitution.SubstitutionMap; import sciwhiz12.janitor.storage.GuildStorage; import sciwhiz12.janitor.utils.Util; @@ -22,22 +24,27 @@ import static sciwhiz12.janitor.Logging.STATUS; public class JanitorBot { private final JDA discord; private final BotConfig config; - private final Messages messages; - private BotConsole console; + private final BotConsole console; private final GuildStorage storage; private final GuildStorage.SavingThread storageSavingThread; - private CommandRegistry cmdRegistry; - private Translations translations; + private final CommandRegistry cmdRegistry; + private final TranslationMap translations; + private final SubstitutionMap substitutions; + private final Messages messages; + private final ReactionManager reactions; public JanitorBot(JDA discord, BotConfig config) { this.config = config; + this.discord = discord; this.console = new BotConsole(this, System.in); this.storage = new GuildStorage(this, Path.of(config.STORAGE_PATH.get())); this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix()); - this.discord = discord; - this.translations = new Translations(this, config.getTranslationsFile()); - this.messages = new Messages(this); - discord.addEventListener(cmdRegistry); + this.translations = new TranslationMap(this, config.getTranslationsFile()); + this.substitutions = new SubstitutionMap(this); + this.messages = new Messages(this, config.getTranslationsFile()); + this.reactions = new ReactionManager(this); + // TODO: find which of these can be loaded in parallel before the bot JDA is ready + discord.addEventListener(cmdRegistry, reactions); discord.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(" n' sweeping n' testing!")); discord.getGuilds().forEach(Guild::loadMembers); JANITOR.info("Ready!"); @@ -65,7 +72,9 @@ public class JanitorBot { return this.config; } - public Messages getMessages() { return this.messages; } + public Messages getMessages() { + return messages; + } public GuildStorage getStorage() { return this.storage; } @@ -73,10 +82,14 @@ public class JanitorBot { return this.cmdRegistry; } - public Translations getTranslations() { + public TranslationMap getTranslations() { return this.translations; } + public ReactionManager getReactionManager() { + return this.reactions; + } + public void shutdown() { JANITOR.info(STATUS, "Shutting down!"); getConfig().getOwnerID() @@ -102,4 +115,8 @@ public class JanitorBot { storage.save(); console.stop(); } + + public SubstitutionMap getSubstitutions() { + return substitutions; + } } diff --git a/src/main/java/sciwhiz12/janitor/Logging.java b/src/main/java/sciwhiz12/janitor/Logging.java index dc38a81..90a570e 100644 --- a/src/main/java/sciwhiz12/janitor/Logging.java +++ b/src/main/java/sciwhiz12/janitor/Logging.java @@ -9,6 +9,7 @@ public class Logging { public static final Marker STATUS = MarkerFactory.getMarker("STATUS"); public static final Marker COMMANDS = MarkerFactory.getMarker("COMMANDS"); public static final Marker TRANSLATIONS = MarkerFactory.getMarker("TRANSLATIONS"); + public static final Marker MESSAGES = MarkerFactory.getMarker("MESSAGES"); public static final Marker STORAGE = MarkerFactory.getMarker("STORAGE"); public static final Logger JANITOR = LoggerFactory.getLogger("janitor"); diff --git a/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java b/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java index b3f0a48..f833510 100644 --- a/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java +++ b/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java @@ -84,8 +84,7 @@ public class CommandRegistry implements EventListener { } JANITOR.debug(COMMANDS, "Executing command."); dispatcher.execute(parseResults); - } - catch (CommandSyntaxException ex) { + } catch (CommandSyntaxException ex) { JANITOR.error(COMMANDS, "Error while parsing message and executing command", ex); } } diff --git a/src/main/java/sciwhiz12/janitor/commands/arguments/GuildMemberArgument.java b/src/main/java/sciwhiz12/janitor/commands/arguments/GuildMemberArgument.java index 3f91b05..63f3c31 100644 --- a/src/main/java/sciwhiz12/janitor/commands/arguments/GuildMemberArgument.java +++ b/src/main/java/sciwhiz12/janitor/commands/arguments/GuildMemberArgument.java @@ -20,8 +20,10 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; public class GuildMemberArgument implements ArgumentType { - public static final SimpleCommandExceptionType UNKNOWN_MEMBER_IDENTIFIER = new SimpleCommandExceptionType(new LiteralMessage("Unknown user identifier")); - public static final SimpleCommandExceptionType MULTIPLE_MEMBERS = new SimpleCommandExceptionType(new LiteralMessage("Too many users, when only one is needed")); + public static final SimpleCommandExceptionType UNKNOWN_MEMBER_IDENTIFIER = new SimpleCommandExceptionType( + new LiteralMessage("Unknown user identifier")); + public static final SimpleCommandExceptionType MULTIPLE_MEMBERS = new SimpleCommandExceptionType( + new LiteralMessage("Too many users, when only one is needed")); public static final Pattern USER_IDENTIFIER_PATTERN = Pattern.compile("<@!?([0-9]+)>"); @@ -105,7 +107,8 @@ public class GuildMemberArgument implements ArgumentType fromGuild(Guild guild) throws CommandSyntaxException { final String nameLowercase = name.toLowerCase(Locale.ROOT); final List members = guild.getMembers().stream() - .filter(member -> member.getUser().getAsTag().replaceAll("\\s", "").toLowerCase(Locale.ROOT).startsWith(nameLowercase)) + .filter(member -> member.getUser().getAsTag().replaceAll("\\s", "").toLowerCase(Locale.ROOT) + .startsWith(nameLowercase)) .collect(Collectors.toList()); if (!multiple && members.size() > 1) { throw MULTIPLE_MEMBERS.create(); diff --git a/src/main/java/sciwhiz12/janitor/commands/misc/HelloCommand.java b/src/main/java/sciwhiz12/janitor/commands/misc/HelloCommand.java index b8425ad..6ab00a6 100644 --- a/src/main/java/sciwhiz12/janitor/commands/misc/HelloCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/misc/HelloCommand.java @@ -24,9 +24,8 @@ public class HelloCommand extends BaseCommand { public LiteralArgumentBuilder getNode() { return literal("greet") - .then( - argument("member", GuildMemberArgument.member()) - .executes(this::run) + .then(argument("member", GuildMemberArgument.member()) + .executes(this::run) ); } @@ -35,11 +34,22 @@ public class HelloCommand extends BaseCommand { final List memberList = getMembers("member", ctx).fromGuild(ctx.getSource().getGuild()); if (memberList.size() == 1) { final Member member = memberList.get(0); - ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!") - .queue( - success -> JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())), - err -> JANITOR.error("Error while sending greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())) - ); + ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!").queue( + success -> { + JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()), + Util.toString(ctx.getSource().getAuthor())); + getBot().getReactionManager().newMessage(success) + .add("\u274C", (msg, event) -> success.delete() + .flatMap(v -> event.getChannel() + .deleteMessageById(ctx.getSource().getMessageIdLong())) + .queue() + ) + .owner(ctx.getSource().getAuthor().getIdLong()) + .create(); + }, + err -> JANITOR.error("Error while sending greeting message to {}, on cmd of {}", + Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())) + ); } } return 1; diff --git a/src/main/java/sciwhiz12/janitor/commands/misc/OKCommand.java b/src/main/java/sciwhiz12/janitor/commands/misc/OKCommand.java index 3bcc4e1..501fa00 100644 --- a/src/main/java/sciwhiz12/janitor/commands/misc/OKCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/misc/OKCommand.java @@ -26,7 +26,8 @@ public class OKCommand extends BaseCommand { .addReaction("\uD83D\uDC4C") .queue( success -> JANITOR.debug("Reacted :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor())), - err -> JANITOR.error("Error while reacting :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor())) + err -> JANITOR + .error("Error while reacting :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor())) ); return 1; } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/BanCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/BanCommand.java index fdeab26..4c624db 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/BanCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/BanCommand.java @@ -8,14 +8,15 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import org.checkerframework.checker.nullness.qual.Nullable; import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.util.ModerationHelper; +import sciwhiz12.janitor.msg.MessageHelper; import java.util.EnumSet; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; @@ -60,43 +61,77 @@ public class BanCommand extends BaseCommand { ); } - public int run(CommandContext ctx, int days, @Nullable String reason) throws CommandSyntaxException { - realRun(ctx, days, reason); - return 1; - } - - void realRun(CommandContext ctx, int days, @Nullable String reason) throws CommandSyntaxException { + int run(CommandContext ctx, int days, @Nullable String reason) throws CommandSyntaxException { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); - return; + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + + return 1; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final List members = getMembers("member", ctx).fromGuild(performer.getGuild()); - if (members.size() < 1) return; + if (members.size() < 1) { return 1; } final Member target = members.get(0); - if (guild.getSelfMember().equals(target)) - messages().GENERAL.cannotActionSelf(channel).queue(); - else if (performer.equals(target)) - messages().GENERAL.cannotActionPerformer(channel, performer).queue(); - else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION)) - messages().GENERAL.insufficientPermissions(channel, BAN_PERMISSION).queue(); - else if (!guild.getSelfMember().canInteract(target)) - messages().GENERAL.cannotInteract(channel, target).queue(); - else if (!performer.hasPermission(BAN_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, BAN_PERMISSION).queue(); - else if (!performer.canInteract(target)) - messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); - else + if (guild.getSelfMember().equals(target)) { + messages().getRegularMessage("general/error/cannot_action_self") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (performer.equals(target)) { + messages().getRegularMessage("general/error/cannot_action_performer") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION)) { + messages().getRegularMessage("general/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", BAN_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!guild.getSelfMember().canInteract(target)) { + messages().getRegularMessage("general/error/cannot_interact") + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else if (!performer.hasPermission(BAN_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", BAN_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!performer.canInteract(target)) { + messages().getRegularMessage("moderation/error/cannot_interact") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else { target.getUser().openPrivateChannel() - .flatMap(dm -> messages().MODERATION.bannedDM(dm, performer, reason)) + .flatMap(dm -> messages().getRegularMessage("moderation/ban/dm") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .with("reason", () -> reason) + .send(getBot(), dm) + ) .mapToResult() - .flatMap(res -> ModerationHelper.banUser(target.getGuild(), performer, target, days, reason) - .flatMap( - v -> messages().MODERATION.banUser(channel, performer, target, reason, days, res.isSuccess()))) + .flatMap(res -> + ModerationHelper.banUser(target.getGuild(), performer, target, days, reason) + .flatMap(v -> messages().getRegularMessage("moderation/ban/info") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .with("private_message", () -> res.isSuccess() ? "✅" : "❌") + .with("delete_duration", () -> String.valueOf(days)) + .with("reason", () -> reason) + .send(getBot(), channel) + ) + ) .queue(); + } + return 1; } } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/KickCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/KickCommand.java index 5996907..71d392b 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/KickCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/KickCommand.java @@ -8,15 +8,16 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import org.checkerframework.checker.nullness.qual.Nullable; import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.util.CommandHelper; import sciwhiz12.janitor.commands.util.ModerationHelper; +import sciwhiz12.janitor.msg.MessageHelper; import java.util.EnumSet; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; import static com.mojang.brigadier.arguments.StringArgumentType.getString; import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; @@ -50,36 +51,72 @@ public class KickCommand extends BaseCommand { private int runWithReason(CommandContext ctx, @Nullable String reason) throws CommandSyntaxException { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return 1; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); + final List members = getMembers("member", ctx).fromGuild(performer.getGuild()); - if (members.size() < 1) { - return 1; - } + if (members.size() < 1) { return 1; } final Member target = members.get(0); - if (guild.getSelfMember().equals(target)) - messages().GENERAL.cannotActionSelf(channel).queue(); - else if (performer.equals(target)) - messages().GENERAL.cannotActionPerformer(channel, performer).queue(); - else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) - messages().GENERAL.insufficientPermissions(channel, KICK_PERMISSION).queue(); - else if (!guild.getSelfMember().canInteract(target)) - messages().GENERAL.cannotInteract(channel, target).queue(); - else if (!performer.hasPermission(KICK_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, KICK_PERMISSION).queue(); - else if (!performer.canInteract(target)) - messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); - else + + if (guild.getSelfMember().equals(target)) { + messages().getRegularMessage("general/error/cannot_action_self") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (performer.equals(target)) { + messages().getRegularMessage("general/error/cannot_action_performer") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) { + messages().getRegularMessage("general/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", KICK_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!guild.getSelfMember().canInteract(target)) { + messages().getRegularMessage("general/error/cannot_interact") + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else if (!performer.hasPermission(KICK_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", KICK_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!performer.canInteract(target)) { + messages().getRegularMessage("moderation/error/cannot_interact") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else { target.getUser().openPrivateChannel() - .flatMap(dm -> messages().MODERATION.kickedDM(dm, performer, target, reason)) + .flatMap(dm -> messages().getRegularMessage("moderation/kick/dm") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .with("reason", () -> reason) + .send(getBot(), dm) + ) .mapToResult() .flatMap(res -> ModerationHelper.kickUser(target.getGuild(), performer, target, reason) - .flatMap( - v -> messages().MODERATION.kickUser(channel, performer, target, reason, res.isSuccess()))) + .flatMap(v -> messages().getRegularMessage("moderation/kick/info") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .with("private_message", () -> res.isSuccess() ? "✅" : "❌") + .with("reason", () -> reason) + .send(getBot(), channel) + ) + ) .queue(); + } return 1; } } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/NoteCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/NoteCommand.java index d73ff39..7bde650 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/NoteCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/NoteCommand.java @@ -1,6 +1,6 @@ package sciwhiz12.janitor.commands.moderation; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableList; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -9,19 +9,21 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import org.checkerframework.checker.nullness.qual.Nullable; import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.moderation.notes.NoteEntry; import sciwhiz12.janitor.moderation.notes.NoteStorage; +import sciwhiz12.janitor.msg.MessageHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; +import javax.annotation.Nullable; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; @@ -32,6 +34,7 @@ import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member; import static sciwhiz12.janitor.commands.moderation.NoteCommand.ModeratorFilter.*; import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.literal; +import static sciwhiz12.janitor.msg.MessageHelper.*; public class NoteCommand extends BaseCommand { public static EnumSet NOTE_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS); @@ -90,32 +93,56 @@ public class NoteCommand extends BaseCommand { } private int addNote(CommandContext ctx, String noteContents) throws CommandSyntaxException { + final MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(ctx.getSource().getChannel()); + messages().getRegularMessage("general/error/guild_only_command") + .apply(user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return 1; } final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Guild guild = performer.getGuild(); - final MessageChannel channel = ctx.getSource().getChannel(); final List members = getMembers("target", ctx).fromGuild(guild); if (members.size() < 1) return 1; final Member target = members.get(0); final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC); - if (guild.getSelfMember().equals(target)) - messages().GENERAL.cannotActionSelf(channel).queue(); - else if (performer.equals(target)) - messages().GENERAL.cannotActionPerformer(channel, performer).queue(); - else if (!performer.hasPermission(NOTE_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue(); - else { + if (guild.getSelfMember().equals(target)) { + messages().getRegularMessage("general/error/cannot_action_self") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (performer.equals(target)) { + messages().getRegularMessage("general/error/cannot_action_performer") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (!performer.hasPermission(NOTE_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", NOTE_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else { final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild); final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get(); if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) { - messages().MODERATION.ERRORS.maxAmountOfNotes(channel, performer, target, maxAmount).queue(); + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .with("notes_amount", () -> String.valueOf(maxAmount)) + .send(getBot(), channel).queue(); + } else { - int noteID = storage.addNote(new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents)); - messages().MODERATION.addNote(channel, performer, target, noteContents, dateTime, noteID).queue(); + final NoteEntry entry = new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents); + int noteID = storage.addNote(entry); + + messages().getRegularMessage("moderation/note/add") + .apply(MessageHelper.member("performer", performer)) + .apply(noteEntry("note_entry", noteID, entry)) + .send(getBot(), channel).queue(); + } } return 1; @@ -127,9 +154,12 @@ public class NoteCommand extends BaseCommand { private int listNotes(CommandContext ctx, boolean filterTarget, ModeratorFilter modFilter) throws CommandSyntaxException { - MessageChannel channel = ctx.getSource().getChannel(); + final MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return 1; } final Guild guild = ctx.getSource().getGuild(); @@ -141,7 +171,10 @@ public class NoteCommand extends BaseCommand { if (members.size() < 1) return 1; final Member target = members.get(0); if (guild.getSelfMember().equals(target)) { - messages().GENERAL.cannotActionSelf(channel).queue(); + messages().getRegularMessage("general/error/cannot_interact") + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + return 1; } predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong()); @@ -156,44 +189,73 @@ public class NoteCommand extends BaseCommand { case PERFORMER: { predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == performer.getIdLong()); } + case NONE: {} } - final OffsetDateTime dateTime = OffsetDateTime.now(); + if (!performer.hasPermission(NOTE_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", NOTE_PERMISSION::toString) + .send(getBot(), channel).queue(); - if (!performer.hasPermission(NOTE_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue(); - else - messages().MODERATION.noteList(channel, NoteStorage.get(getBot().getStorage(), guild) - .getNotes() - .entrySet().stream() - .filter(predicate) - .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)) - ).queue(); + } else { + messages().>getListingMessage("moderation/note/list") + .apply(MessageHelper.member("performer", performer)) + .amountPerPage(8) + .setEntryApplier((entry, subs) -> subs + .with("note_entry.note_id", () -> String.valueOf(entry.getKey())) + .apply(user("note_entry.performer", entry.getValue().getPerformer())) + .apply(user("note_entry.target", entry.getValue().getTarget())) + .with("note_entry.date_time", () -> entry.getValue().getDateTime().format(DATE_TIME_FORMAT)) + .with("note_entry.contents", entry.getValue()::getContents) + ) + .build(channel, getBot(), ctx.getSource().getMessage(), + NoteStorage.get(getBot().getStorage(), guild) + .getNotes() + .entrySet().stream() + .filter(predicate) + .sorted(Comparator.>comparingInt(Map.Entry::getKey).reversed()) + .collect(ImmutableList.toImmutableList()) + ); + } return 1; } private int removeNote(CommandContext ctx, int noteID) { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return 1; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); - final OffsetDateTime dateTime = OffsetDateTime.now(); + if (!performer.hasPermission(NOTE_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", NOTE_PERMISSION::toString) + .send(getBot(), channel).queue(); - if (!performer.hasPermission(NOTE_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue(); - else { + } else { final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild); @Nullable final NoteEntry entry = storage.getNote(noteID); - if (entry == null) - messages().MODERATION.ERRORS.noNoteFound(channel, performer, noteID).queue(); - else { + if (entry == null) { + messages().getRegularMessage("moderation/note/add") + .apply(MessageHelper.member("performer", performer)) + .with("note_id", () -> String.valueOf(noteID)) + .send(getBot(), channel).queue(); + + } else { storage.removeNote(noteID); - messages().MODERATION.removeNote(channel, performer, noteID, entry).queue(); + + messages().getRegularMessage("moderation/note/remove") + .apply(MessageHelper.member("performer", performer)) + .apply(noteEntry("note_entry", noteID, entry)) + .send(getBot(), channel).queue(); } } return 1; diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/UnbanCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/UnbanCommand.java index e909e7c..7cdcfa8 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/UnbanCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/UnbanCommand.java @@ -12,6 +12,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.util.ModerationHelper; +import sciwhiz12.janitor.msg.MessageHelper; import java.util.EnumSet; import java.util.Locale; @@ -49,7 +50,10 @@ public class UnbanCommand extends BaseCommand { void realNamedRun(CommandContext ctx) { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return; } final Guild guild = ctx.getSource().getGuild(); @@ -62,10 +66,14 @@ public class UnbanCommand extends BaseCommand { .startsWith(username)) .collect(Collectors.toList())) .queue(bans -> { - if (bans.size() > 1) - messages().GENERAL.ambiguousMember(channel).queue(); - else if (bans.size() == 1) + if (bans.size() > 1) { + messages().getRegularMessage("general/error/ambiguous_member") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + + } else if (bans.size() == 1) { tryUnban(channel, guild, performer, bans.get(0).getUser()); + } }); } @@ -77,7 +85,10 @@ public class UnbanCommand extends BaseCommand { void realIdRun(CommandContext ctx) { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return; } final Guild guild = ctx.getSource().getGuild(); @@ -97,13 +108,26 @@ public class UnbanCommand extends BaseCommand { } void tryUnban(MessageChannel channel, Guild guild, Member performer, User target) { - if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) - messages().GENERAL.insufficientPermissions(channel, UNBAN_PERMISSION).queue(); - else if (!performer.hasPermission(UNBAN_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, UNBAN_PERMISSION).queue(); - else + if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) { + messages().getRegularMessage("general/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", UNBAN_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!performer.hasPermission(UNBAN_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", UNBAN_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else { ModerationHelper.unbanUser(guild, target) - .flatMap(v -> messages().MODERATION.unbanUser(channel, performer, target)) + .flatMap(v -> messages().getRegularMessage("moderation/unban/info") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.user("target", target)) + .send(getBot(), channel) + ) .queue(); + } } } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java index 27eb3ec..0790e0b 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java @@ -8,15 +8,15 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import org.checkerframework.checker.nullness.qual.Nullable; import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningStorage; +import sciwhiz12.janitor.msg.MessageHelper; -import java.time.OffsetDateTime; import java.util.EnumSet; import java.util.Objects; +import javax.annotation.Nullable; import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.literal; @@ -45,34 +45,54 @@ public class UnwarnCommand extends BaseCommand { void realRun(CommandContext ctx) { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + return; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); int caseID = IntegerArgumentType.getInteger(ctx, "caseId"); - final OffsetDateTime dateTime = OffsetDateTime.now(); + if (!performer.hasPermission(WARN_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", WARN_PERMISSION::toString) + .send(getBot(), channel).queue(); - if (!performer.hasPermission(WARN_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); - else { + } else { final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild); @Nullable final WarningEntry entry = storage.getWarning(caseID); Member temp; - if (entry == null) - messages().MODERATION.ERRORS.noWarnWithID(channel, performer, caseID).queue(); - else if (entry.getWarned().getIdLong() == performer.getIdLong() - && !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) - messages().MODERATION.ERRORS.cannotUnwarnSelf(channel, performer, caseID, entry).queue(); - else if (config().WARNINGS_RESPECT_MOD_ROLES.get() - && (temp = guild.getMember(entry.getPerformer())) != null - && !performer.canInteract(temp)) - messages().MODERATION.ERRORS.cannotRemoveHigherModerated(channel, performer, caseID, entry).queue(); - else { + if (entry == null) { + messages().getRegularMessage("moderation/error/unwarn/no_case_found") + .apply(MessageHelper.member("performer", performer)) + .with("case_id", () -> String.valueOf(caseID)) + .send(getBot(), channel).queue(); + + } else if (entry.getWarned().getIdLong() == performer.getIdLong() + && !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) { + messages().getRegularMessage("moderation/error/unwarn/cannot_unwarn_self") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.warningEntry("warning_entry", caseID, entry)) + .send(getBot(), channel).queue(); + + } else if (config().WARNINGS_RESPECT_MOD_ROLES.get() + && (temp = guild.getMember(entry.getPerformer())) != null && !performer.canInteract(temp)) { + messages().getRegularMessage("moderation/error/unwarn/cannot_remove_higher_mod") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.warningEntry("warning_entry", caseID, entry)) + .send(getBot(), channel).queue(); + + } else { storage.removeWarning(caseID); - messages().MODERATION.unwarn(channel, performer, caseID, entry).queue(); + messages().getRegularMessage("moderation/unwarn/info") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.warningEntry("warning_entry", caseID, entry)) + .send(getBot(), channel).queue(); + } } } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java index 70199fc..c0288a3 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java @@ -12,6 +12,7 @@ import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningStorage; +import sciwhiz12.janitor.msg.MessageHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -44,45 +45,70 @@ public class WarnCommand extends BaseCommand { ); } - public int run(CommandContext ctx, String reason) throws CommandSyntaxException { - realRun(ctx, reason); - return 1; - } - - void realRun(CommandContext ctx, String reason) throws CommandSyntaxException { + int run(CommandContext ctx, String reason) throws CommandSyntaxException { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); - return; + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + + return 1; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final List members = getMembers("member", ctx).fromGuild(performer.getGuild()); - if (members.size() < 1) return; + if (members.size() < 1) { return 1; } final Member target = members.get(0); final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC); - if (guild.getSelfMember().equals(target)) - messages().GENERAL.cannotActionSelf(channel).queue(); - else if (performer.equals(target)) - messages().GENERAL.cannotActionPerformer(channel, performer).queue(); - else if (!performer.hasPermission(WARN_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); - else if (!performer.canInteract(target)) - messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); - else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) - messages().MODERATION.ERRORS.cannotWarnMods(channel, performer, target).queue(); - else + if (guild.getSelfMember().equals(target)) { + messages().getRegularMessage("general/error/cannot_action_self") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (performer.equals(target)) { + messages().getRegularMessage("general/error/cannot_action_performer") + .apply(MessageHelper.member("performer", performer)) + .send(getBot(), channel).queue(); + + } else if (!performer.hasPermission(WARN_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", WARN_PERMISSION::toString) + .send(getBot(), channel).queue(); + + } else if (!performer.canInteract(target)) { + messages().getRegularMessage("moderation/error/cannot_interact") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) { + messages().getRegularMessage("moderation/error/warn/cannot_warn_mods") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + } else { + WarningEntry entry = new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason); + int caseId = WarningStorage.get(getBot().getStorage(), guild).addWarning(entry); + target.getUser().openPrivateChannel() - .flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime)) + .flatMap(dm -> messages().getRegularMessage("moderation/warn/dm") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.warningEntry("warning_entry", caseId, entry)) + .send(getBot(), dm) + ) .mapToResult() - .flatMap(res -> { - int caseId = WarningStorage.get(getBot().getStorage(), guild) - .addWarning(new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason)); - return messages().MODERATION - .warnUser(channel, performer, target, reason, dateTime, caseId, res.isSuccess()); - }) + .flatMap(res -> messages().getRegularMessage("moderation/warn/info") + .apply(MessageHelper.member("performer", performer)) + .apply(MessageHelper.warningEntry("warning_entry", caseId, entry)) + .with("private_message", () -> res.isSuccess() ? "✅" : "❌") + .send(getBot(), channel) + ) .queue(); + } + return 1; } } diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java index bfa4daf..0439c6e 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java @@ -1,6 +1,6 @@ package sciwhiz12.janitor.commands.moderation; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableList; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -13,8 +13,9 @@ import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningStorage; +import sciwhiz12.janitor.msg.MessageHelper; -import java.time.OffsetDateTime; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -25,6 +26,8 @@ import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.getMember import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member; import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.literal; +import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT; +import static sciwhiz12.janitor.msg.MessageHelper.user; public class WarnListCommand extends BaseCommand { public static final EnumSet WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS); @@ -54,18 +57,15 @@ public class WarnListCommand extends BaseCommand { .executes(ctx -> this.run(ctx, false, false)); } - public int run(CommandContext ctx, boolean filterTarget, boolean filterModerator) - throws CommandSyntaxException { - realRun(ctx, filterTarget, filterModerator); - return 1; - } - - void realRun(CommandContext ctx, boolean filterTarget, boolean filterModerator) + int run(CommandContext ctx, boolean filterTarget, boolean filterModerator) throws CommandSyntaxException { MessageChannel channel = ctx.getSource().getChannel(); if (!ctx.getSource().isFromGuild()) { - messages().GENERAL.guildOnlyCommand(channel).queue(); - return; + messages().getRegularMessage("general/error/guild_only_command") + .apply(MessageHelper.user("performer", ctx.getSource().getAuthor())) + .send(getBot(), channel).queue(); + + return 1; } final Guild guild = ctx.getSource().getGuild(); final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); @@ -73,31 +73,50 @@ public class WarnListCommand extends BaseCommand { if (filterTarget) { final List members = getMembers("target", ctx).fromGuild(performer.getGuild()); - if (members.size() < 1) return; + if (members.size() < 1) return 1; final Member target = members.get(0); if (guild.getSelfMember().equals(target)) { - messages().GENERAL.cannotActionSelf(channel).queue(); - return; + messages().getRegularMessage("general/error/cannot_interact") + .apply(MessageHelper.member("target", target)) + .send(getBot(), channel).queue(); + + return 1; } predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong()); } if (filterModerator) { final List members = getMembers("moderator", ctx).fromGuild(performer.getGuild()); - if (members.size() < 1) return; + if (members.size() < 1) return 1; final Member mod = members.get(0); predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == mod.getIdLong()); } - final OffsetDateTime dateTime = OffsetDateTime.now(); + if (!performer.hasPermission(WARN_PERMISSION)) { + messages().getRegularMessage("moderation/error/insufficient_permissions") + .apply(MessageHelper.member("performer", performer)) + .with("required_permissions", WARN_PERMISSION::toString) + .send(getBot(), channel).queue(); - if (!performer.hasPermission(WARN_PERMISSION)) - messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); - else - messages().MODERATION.warnList(channel, WarningStorage.get(getBot().getStorage(), guild) - .getWarnings() - .entrySet().stream() - .filter(predicate) - .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)) - ).queue(); + } else { + messages().>getListingMessage("moderation/warn/list") + .apply(MessageHelper.member("performer", performer)) + .amountPerPage(8) + .setEntryApplier((entry, subs) -> subs + .with("warning_entry.case_id", () -> String.valueOf(entry.getKey())) + .apply(user("warning_entry.performer", entry.getValue().getPerformer())) + .apply(user("warning_entry.warned", entry.getValue().getWarned())) + .with("warning_entry.date_time", () -> entry.getValue().getDateTime().format(DATE_TIME_FORMAT)) + .with("warning_entry.reason", entry.getValue()::getReason) + ) + .build(channel, getBot(), ctx.getSource().getMessage(), + WarningStorage.get(getBot().getStorage(), guild) + .getWarnings() + .entrySet().stream() + .filter(predicate) + .sorted(Comparator.>comparingInt(Map.Entry::getKey).reversed()) + .collect(ImmutableList.toImmutableList()) + ); + } + return 1; } } diff --git a/src/main/java/sciwhiz12/janitor/commands/util/ModerationHelper.java b/src/main/java/sciwhiz12/janitor/commands/util/ModerationHelper.java index 14ea445..069d503 100644 --- a/src/main/java/sciwhiz12/janitor/commands/util/ModerationHelper.java +++ b/src/main/java/sciwhiz12/janitor/commands/util/ModerationHelper.java @@ -4,12 +4,12 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; -import org.checkerframework.checker.nullness.qual.Nullable; import java.time.Instant; import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; +import javax.annotation.Nullable; +import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT; import static sciwhiz12.janitor.utils.Util.nameFor; public class ModerationHelper { @@ -18,7 +18,7 @@ public class ModerationHelper { auditReason.append("Kicked by ") .append(nameFor(performer.getUser())) .append(" on ") - .append(Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME)); + .append(Instant.now().atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT)); if (reason != null) auditReason.append(" for reason: ").append(reason); return guild.kick(target, auditReason.toString()); @@ -30,7 +30,7 @@ public class ModerationHelper { auditReason.append("Banned by ") .append(nameFor(performer.getUser())) .append(" on ") - .append(Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME)); + .append(Instant.now().atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT)); if (reason != null) auditReason.append(" for reason: ").append(reason); return guild.ban(target, deleteDuration, auditReason.toString()); diff --git a/src/main/java/sciwhiz12/janitor/config/BotConfig.java b/src/main/java/sciwhiz12/janitor/config/BotConfig.java index 8c73d3b..6dc0fb7 100644 --- a/src/main/java/sciwhiz12/janitor/config/BotConfig.java +++ b/src/main/java/sciwhiz12/janitor/config/BotConfig.java @@ -4,11 +4,11 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.electronwill.nightconfig.core.file.FileNotFoundAction; import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.toml.TomlFormat; -import org.checkerframework.checker.nullness.qual.Nullable; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; +import javax.annotation.Nullable; import static sciwhiz12.janitor.Logging.CONFIG; import static sciwhiz12.janitor.Logging.JANITOR; @@ -23,6 +23,7 @@ public class BotConfig { public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL; public final CommentedConfigSpec.ConfigValue CUSTOM_TRANSLATION_FILE; + public final CommentedConfigSpec.ConfigValue CUSTOM_MESSAGES_DIRECTORY; public final CommentedConfigSpec.ConfigValue COMMAND_PREFIX; @@ -67,6 +68,10 @@ public class BotConfig { .comment("A file which contains custom translation keys to load for messages.", "If blank, no file shall be loaded.") .define("messages.custom_translations", ""); + CUSTOM_MESSAGES_DIRECTORY = builder + .comment("A folder containing custom messages, with a 'messages.json' key file.", + "If blank, no folder shall be loaded and defaults will be used.") + .define("messages.custom_messages", ""); COMMAND_PREFIX = builder .comment("The prefix for commands.") @@ -115,8 +120,7 @@ public class BotConfig { spec.setConfig(config); // TODO: config spec FileWatcher.defaultInstance().addWatch(configPath, this::onFileChange); - } - catch (IOException ex) { + } catch (IOException ex) { JANITOR.error("Error while building config from file {}", configPath, ex); } } @@ -134,6 +138,15 @@ public class BotConfig { .orElse(null); } + @Nullable + public Path getMessagesFolder() { + return options.getMessagesFolder(). + or(() -> CUSTOM_MESSAGES_DIRECTORY.get().isBlank() ? + Optional.empty() : + Optional.of(Path.of(CUSTOM_MESSAGES_DIRECTORY.get()))) + .orElse(null); + } + public String getToken() { return options.getToken().orElse(CLIENT_TOKEN.get()); } @@ -157,8 +170,7 @@ public class BotConfig { CONFIG.info("Reloading config due to file change {}", configPath); config.load(); spec.setConfig(config); - } - catch (Exception ex) { + } catch (Exception ex) { CONFIG.error("Error while reloading config from {}", configPath, ex); } } diff --git a/src/main/java/sciwhiz12/janitor/config/BotOptions.java b/src/main/java/sciwhiz12/janitor/config/BotOptions.java index a61fb0c..ff51f26 100644 --- a/src/main/java/sciwhiz12/janitor/config/BotOptions.java +++ b/src/main/java/sciwhiz12/janitor/config/BotOptions.java @@ -14,6 +14,7 @@ public class BotOptions { private final OptionSet options; private final ArgumentAcceptingOptionSpec configPath; private final ArgumentAcceptingOptionSpec translationsPath; + private final ArgumentAcceptingOptionSpec messagesFolder; private final ArgumentAcceptingOptionSpec token; private final ArgumentAcceptingOptionSpec prefix; private final ArgumentAcceptingOptionSpec owner; @@ -28,6 +29,10 @@ public class BotOptions { .accepts("translations", "The path to the translations file") .withRequiredArg() .withValuesConvertedBy(new PathConverter(FILE_EXISTING, READABLE)); + this.messagesFolder = parser + .accepts("translations", "The path to the custom messages folder") + .withRequiredArg() + .withValuesConvertedBy(new PathConverter(DIRECTORY_EXISTING, READABLE)); this.token = parser .accepts("token", "The Discord token for the bot user") .withRequiredArg(); @@ -49,6 +54,10 @@ public class BotOptions { return translationsPath.valueOptional(options); } + public Optional getMessagesFolder() { + return messagesFolder.valueOptional(options); + } + public Optional getToken() { return token.valueOptional(options); } diff --git a/src/main/java/sciwhiz12/janitor/config/CommentedConfigSpec.java b/src/main/java/sciwhiz12/janitor/config/CommentedConfigSpec.java index ccfd34e..4236b1b 100644 --- a/src/main/java/sciwhiz12/janitor/config/CommentedConfigSpec.java +++ b/src/main/java/sciwhiz12/janitor/config/CommentedConfigSpec.java @@ -134,8 +134,7 @@ public class CommentedConfigSpec extends UnmodifiableConfigWrapper, JsonSerializer { - private final JanitorBot bot; + public static class Serializer extends StdSerializer { + private static final long serialVersionUID = 1L; - public Serializer(JanitorBot bot) { + public Serializer() { + super(NoteEntry.class); + } + + @Override + public void serialize(NoteEntry value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("performer", value.getPerformer().getIdLong()); + gen.writeNumberField("target", value.getTarget().getIdLong()); + gen.writeStringField("dateTime", value.getDateTime().toString()); + gen.writeStringField("contents", value.getContents()); + gen.writeEndObject(); + } + } + + public static class Deserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + private final Supplier bot; + + public Deserializer(Supplier bot) { + super(NoteEntry.class); this.bot = bot; } @Override - public NoteEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - final JsonObject obj = json.getAsJsonObject(); - final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete(); - final User target = bot.getDiscord().retrieveUserById(obj.get("target").getAsLong()).complete(); - final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString()); - final String reason = obj.get("contents").getAsString(); - return new NoteEntry(performer, target, dateTime, reason); - } - - @Override - public JsonElement serialize(NoteEntry src, Type typeOfSrc, JsonSerializationContext context) { - final JsonObject obj = new JsonObject(); - obj.addProperty("performer", src.getPerformer().getId()); - obj.addProperty("target", src.getTarget().getId()); - obj.addProperty("dateTime", src.getDateTime().toString()); - obj.addProperty("contents", src.getContents()); - return obj; + public NoteEntry deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + final JsonNode obj = ctx.readTree(p); + User performer = bot.get().getDiscord().retrieveUserById(obj.get("performer").asLong()).complete(); + User target = bot.get().getDiscord().retrieveUserById(obj.get("target").asLong()).complete(); + OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").asText()); + String contents = obj.get("contents").asText(); + return new NoteEntry(performer, target, dateTime, contents); } } } diff --git a/src/main/java/sciwhiz12/janitor/moderation/notes/NoteStorage.java b/src/main/java/sciwhiz12/janitor/moderation/notes/NoteStorage.java index 5b8f384..78a583a 100644 --- a/src/main/java/sciwhiz12/janitor/moderation/notes/NoteStorage.java +++ b/src/main/java/sciwhiz12/janitor/moderation/notes/NoteStorage.java @@ -1,11 +1,11 @@ package sciwhiz12.janitor.moderation.notes; import com.electronwill.nightconfig.core.utils.ObservedMap; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ObjectNode; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,28 +14,24 @@ import sciwhiz12.janitor.storage.GuildStorage; import sciwhiz12.janitor.storage.JsonStorage; import sciwhiz12.janitor.storage.StorageKey; -import java.lang.reflect.Type; +import java.io.IOException; import java.util.HashMap; import java.util.Map; public class NoteStorage extends JsonStorage { - private static final Type NOTE_MAP_TYPE = new TypeToken>() {}.getType(); + private static final TypeReference> NOTE_MAP_TYPE = new TypeReference<>() {}; public static final StorageKey KEY = new StorageKey<>("notes", NoteStorage.class); public static NoteStorage get(GuildStorage storage, Guild guild) { return storage.getOrCreate(guild, KEY, () -> new NoteStorage(storage.getBot())); } - private final Gson gson; private final JanitorBot bot; private int lastID = 1; private final Map notes = new ObservedMap<>(new HashMap<>(), this::markDirty); public NoteStorage(JanitorBot bot) { this.bot = bot; - this.gson = new GsonBuilder() - .registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot)) - .create(); } public JanitorBot getBot() { @@ -53,14 +49,14 @@ public class NoteStorage extends JsonStorage { return notes.get(noteID); } - public NoteEntry removeNote(int noteID) { - return notes.remove(noteID); + public void removeNote(int noteID) { + notes.remove(noteID); } public int getAmountOfNotes(User target) { return (int) notes.values().stream() - .filter(entry -> entry.getTarget() == target) - .count(); + .filter(entry -> entry.getTarget() == target) + .count(); } public Map getNotes() { @@ -68,18 +64,27 @@ public class NoteStorage extends JsonStorage { } @Override - public JsonElement save() { - JsonObject obj = new JsonObject(); - obj.addProperty("lastNoteID", lastID); - obj.add("notes", gson.toJsonTree(notes)); + protected void initialize(ObjectMapper mapper) { + super.initialize(mapper); + mapper.registerModule( + new SimpleModule() + .addSerializer(NoteEntry.class, new NoteEntry.Serializer()) + .addDeserializer(NoteEntry.class, new NoteEntry.Deserializer(this::getBot)) + ); + } + + @Override + public JsonNode save(ObjectMapper mapper) { + final ObjectNode obj = mapper.createObjectNode(); + obj.put("lastNoteID", lastID); + obj.set("notes", mapper.valueToTree(notes)); return obj; } @Override - public void load(JsonElement in) { - final JsonObject obj = in.getAsJsonObject(); - lastID = obj.get("lastNoteID").getAsInt(); - final Map loaded = gson.fromJson(obj.get("notes"), NOTE_MAP_TYPE); + public void load(JsonNode in, ObjectMapper mapper) throws IOException { + lastID = in.get("lastNoteID").asInt(); + final Map loaded = mapper.readerFor(NOTE_MAP_TYPE).readValue(in.get("notes")); notes.clear(); notes.putAll(loaded); } diff --git a/src/main/java/sciwhiz12/janitor/moderation/warns/WarningEntry.java b/src/main/java/sciwhiz12/janitor/moderation/warns/WarningEntry.java index e136c77..2623c2d 100644 --- a/src/main/java/sciwhiz12/janitor/moderation/warns/WarningEntry.java +++ b/src/main/java/sciwhiz12/janitor/moderation/warns/WarningEntry.java @@ -1,18 +1,19 @@ package sciwhiz12.janitor.moderation.warns; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import net.dv8tion.jda.api.entities.User; import sciwhiz12.janitor.JanitorBot; -import java.lang.reflect.Type; +import java.io.IOException; import java.time.OffsetDateTime; import java.util.Objects; +import java.util.function.Supplier; import javax.annotation.Nullable; public class WarningEntry { @@ -62,35 +63,42 @@ public class WarningEntry { return Objects.hash(getPerformer(), getWarned(), getDateTime(), getReason()); } - public static class Serializer implements JsonDeserializer, JsonSerializer { - private final JanitorBot bot; + public static class Serializer extends StdSerializer { + private static final long serialVersionUID = 1L; - public Serializer(JanitorBot bot) { + public Serializer() { + super(WarningEntry.class); + } + + @Override + public void serialize(WarningEntry value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("performer", value.getPerformer().getIdLong()); + gen.writeNumberField("warned", value.getWarned().getIdLong()); + gen.writeStringField("dateTime", value.getDateTime().toString()); + gen.writeStringField("reason", value.getReason()); + gen.writeEndObject(); + } + } + + public static class Deserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + private final Supplier bot; + + public Deserializer(Supplier bot) { + super(WarningEntry.class); this.bot = bot; } @Override - public WarningEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - final JsonObject obj = json.getAsJsonObject(); - final User warned = bot.getDiscord().retrieveUserById(obj.get("warned").getAsLong()).complete(); - final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete(); - final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString()); - @Nullable - final String reason = obj.has("reason") ? obj.get("reason").getAsString() : null; - return new WarningEntry(warned, performer, dateTime, reason); - } - - @Override - public JsonElement serialize(WarningEntry src, Type typeOfSrc, JsonSerializationContext context) { - final JsonObject obj = new JsonObject(); - obj.addProperty("warned", src.getWarned().getId()); - obj.addProperty("performer", src.getPerformer().getId()); - obj.addProperty("dateTime", src.getDateTime().toString()); - if (src.getReason() != null) { - obj.addProperty("reason", src.getReason()); - } - return obj; + public WarningEntry deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + final JsonNode obj = ctx.readTree(p); + User performer = bot.get().getDiscord().retrieveUserById(obj.get("performer").asLong()).complete(); + User warned = bot.get().getDiscord().retrieveUserById(obj.get("warned").asLong()).complete(); + OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").asText()); + String contents = obj.get("reason").asText(); + return new WarningEntry(performer, warned, dateTime, contents); } } } diff --git a/src/main/java/sciwhiz12/janitor/moderation/warns/WarningStorage.java b/src/main/java/sciwhiz12/janitor/moderation/warns/WarningStorage.java index 219d843..041ef5a 100644 --- a/src/main/java/sciwhiz12/janitor/moderation/warns/WarningStorage.java +++ b/src/main/java/sciwhiz12/janitor/moderation/warns/WarningStorage.java @@ -1,11 +1,11 @@ package sciwhiz12.janitor.moderation.warns; import com.electronwill.nightconfig.core.utils.ObservedMap; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ObjectNode; import net.dv8tion.jda.api.entities.Guild; import org.checkerframework.checker.nullness.qual.Nullable; import sciwhiz12.janitor.JanitorBot; @@ -13,28 +13,23 @@ import sciwhiz12.janitor.storage.GuildStorage; import sciwhiz12.janitor.storage.JsonStorage; import sciwhiz12.janitor.storage.StorageKey; -import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; public class WarningStorage extends JsonStorage { - private static final Type WARNING_MAP_TYPE = new TypeToken>() {}.getType(); + private static final TypeReference> WARNING_MAP_TYPE = new TypeReference<>() {}; public static final StorageKey KEY = new StorageKey<>("warnings", WarningStorage.class); public static WarningStorage get(GuildStorage storage, Guild guild) { return storage.getOrCreate(guild, KEY, () -> new WarningStorage(storage.getBot())); } - private final Gson gson; private final JanitorBot bot; private int lastID = 1; private final Map warnings = new ObservedMap<>(new HashMap<>(), this::markDirty); public WarningStorage(JanitorBot bot) { this.bot = bot; - this.gson = new GsonBuilder() - .registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot)) - .create(); } public JanitorBot getBot() { @@ -52,8 +47,8 @@ public class WarningStorage extends JsonStorage { return warnings.get(caseID); } - public WarningEntry removeWarning(int caseID) { - return warnings.remove(caseID); + public void removeWarning(int caseID) { + warnings.remove(caseID); } public Map getWarnings() { @@ -61,18 +56,27 @@ public class WarningStorage extends JsonStorage { } @Override - public JsonElement save() { - JsonObject obj = new JsonObject(); - obj.addProperty("lastCaseID", lastID); - obj.add("warnings", gson.toJsonTree(warnings)); + protected void initialize(ObjectMapper mapper) { + super.initialize(mapper); + mapper.registerModule( + new SimpleModule() + .addSerializer(WarningEntry.class, new WarningEntry.Serializer()) + .addDeserializer(WarningEntry.class, new WarningEntry.Deserializer(this::getBot)) + ); + } + + @Override + public JsonNode save(ObjectMapper mapper) { + final ObjectNode obj = mapper.createObjectNode(); + obj.put("lastCaseID", lastID); + obj.set("warnings", mapper.valueToTree(warnings)); return obj; } @Override - public void load(JsonElement in) { - final JsonObject obj = in.getAsJsonObject(); - lastID = obj.get("lastCaseID").getAsInt(); - final Map loaded = gson.fromJson(obj.get("warnings"), WARNING_MAP_TYPE); + public void load(JsonNode in, ObjectMapper mapper) { + lastID = in.get("lastCaseID").asInt(); + final Map loaded = mapper.convertValue(in.get("warnings"), WARNING_MAP_TYPE); warnings.clear(); warnings.putAll(loaded); } diff --git a/src/main/java/sciwhiz12/janitor/msg/General.java b/src/main/java/sciwhiz12/janitor/msg/General.java deleted file mode 100644 index 2ad3a95..0000000 --- a/src/main/java/sciwhiz12/janitor/msg/General.java +++ /dev/null @@ -1,79 +0,0 @@ -package sciwhiz12.janitor.msg; - -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageChannel; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.requests.RestAction; - -import java.util.EnumSet; -import java.util.stream.Collectors; - -public final class General { - private final Messages messages; - - General(Messages messages) { - this.messages = messages; - } - - private String translate(String key, Object... args) { - return messages.translate(key, args); - } - - public RestAction guildOnlyCommand(MessageChannel channel) { - return channel.sendMessage( - messages.failureEmbed(translate("general.guild_only_command.title")) - .setDescription(translate("general.guild_only_command.desc")) - .build() - ); - } - - public RestAction insufficientPermissions(MessageChannel channel, EnumSet permissions) { - return channel.sendMessage( - messages.failureEmbed(translate("general.insufficient_permissions.title")) - .setDescription(translate("general.insufficient_permissions.desc")) - .addField(new MessageEmbed.Field( - translate("general.insufficient_permissions.field.permissions"), - permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")), - false)) - .build() - ); - } - - public RestAction ambiguousMember(MessageChannel channel) { - return channel.sendMessage( - messages.failureEmbed(translate("general.ambiguous_member.title")) - .setDescription(translate("general.ambiguous_member.desc")) - .build() - ); - } - - public RestAction cannotInteract(MessageChannel channel, Member target) { - return channel.sendMessage( - messages.failureEmbed(translate("general.cannot_interact.title")) - .setDescription(translate("general.cannot_interact.desc")) - .addField(translate("general.cannot_interact.field.target"), target.getAsMention(), true) - .build() - ); - } - - public RestAction cannotActionSelf(MessageChannel channel) { - return channel.sendMessage( - messages.failureEmbed(translate("general.cannot_action_self.title")) - .setDescription(translate("general.cannot_action_self.desc")) - .build() - ); - } - - public RestAction cannotActionPerformer(MessageChannel channel, Member performer) { - return channel.sendMessage( - messages.failureEmbed(translate("general.cannot_action_performer.title")) - .setDescription(translate("general.cannot_action_performer.desc")) - .addField(translate("general.cannot_action_performer.field.performer"), - performer.getUser().getAsMention(), - true) - .build() - ); - } -} diff --git a/src/main/java/sciwhiz12/janitor/msg/ListingMessageBuilder.java b/src/main/java/sciwhiz12/janitor/msg/ListingMessageBuilder.java new file mode 100644 index 0000000..855b4ab --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/ListingMessageBuilder.java @@ -0,0 +1,161 @@ +package sciwhiz12.janitor.msg; + +import com.google.common.collect.ImmutableList; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.MessageEmbed; +import sciwhiz12.janitor.JanitorBot; +import sciwhiz12.janitor.msg.json.ListingMessage; +import sciwhiz12.janitor.msg.substitution.CustomSubstitutions; +import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions; +import sciwhiz12.janitor.msg.substitution.SubstitutionMap; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class ListingMessageBuilder implements IHasCustomSubstitutions> { + private final ListingMessage message; + private final Map> customSubstitutions; + private int amountPerPage = 10; + private BiConsumer entryApplier = (entry, sub) -> {}; + + public ListingMessageBuilder(ListingMessage message, Map> customSubstitutions) { + this.message = message; + this.customSubstitutions = customSubstitutions; + } + + public ListingMessageBuilder(ListingMessage message) { + this(message, new HashMap<>()); + } + + public ListingMessageBuilder amountPerPage(int amountPerPage) { + this.amountPerPage = amountPerPage; + return this; + } + + public ListingMessageBuilder setEntryApplier(BiConsumer entryApplier) { + this.entryApplier = entryApplier; + return this; + } + + public ListingMessageBuilder apply(Consumer> consumer) { + consumer.accept(this); + return this; + } + + public ListingMessageBuilder with(final String argument, final Supplier value) { + this.customSubstitutions.put(argument, value); + return this; + } + + public void build(MessageChannel channel, TranslationMap translations, SubstitutionMap globalSubstitutions, + Message triggerMessage, List entries) { + + final CustomSubstitutions customSubs = globalSubstitutions.with(customSubstitutions); + final ImmutableList list = ImmutableList.copyOf(entries); + final PagedMessage pagedMessage = new PagedMessage(message, list, amountPerPage); + + channel.sendMessage(pagedMessage.createMessage(translations, customSubs, entryApplier)) + .queue(listMsg -> translations.getBot().getReactionManager().newMessage(listMsg) + .owner(triggerMessage.getAuthor().getIdLong()) + .removeEmotes(true) + .add("\u2b05", (msg, event) -> { // PREVIOUS + if (pagedMessage.advancePage(PageDirection.PREVIOUS)) { + event.retrieveMessage() + .flatMap(eventMsg -> eventMsg.editMessage( + pagedMessage.createMessage(translations, customSubs, entryApplier)) + ) + .queue(); + } + }) + .add("\u274c", (msg, event) -> { // CLOSE + event.getChannel().deleteMessageById(event.getMessageIdLong()) + .flatMap(v -> !triggerMessage.isFromGuild() || + event.getGuild().getSelfMember() + .hasPermission(triggerMessage.getTextChannel(), + Permission.MESSAGE_MANAGE), + v -> triggerMessage.delete()) + .queue(); + }) + .add("\u27a1", (msg, event) -> { // NEXT + if (pagedMessage.advancePage(PageDirection.NEXT)) { + event.retrieveMessage() + .flatMap(eventMsg -> eventMsg.editMessage( + pagedMessage.createMessage(translations, customSubs, entryApplier)) + ) + .queue(); + } + }) + .create() + ); + } + + public void build(MessageChannel channel, JanitorBot bot, Message triggerMessage, List entries) { + build(channel, bot.getTranslations(), bot.getSubstitutions(), triggerMessage, entries); + } + + class PagedMessage { + private final ListingMessage message; + private final ImmutableList list; + private final int maxPages; + private final int amountPerPage; + private int currentPage = 0; + private int lastPage = -1; + private EmbedBuilder cachedMessage; + + PagedMessage(ListingMessage message, ImmutableList list, int amountPerPage) { + this.message = message; + this.list = list; + this.amountPerPage = amountPerPage; + this.maxPages = Math.floorDiv(list.size(), ListingMessageBuilder.this.amountPerPage); + } + + public int getMaxPages() { + return maxPages; + } + + public int getCurrentPage() { + return currentPage; + } + + public boolean advancePage(PageDirection direction) { + if (direction == PageDirection.PREVIOUS && currentPage > 0) { + currentPage -= 1; + return true; + } else if (direction == PageDirection.NEXT && currentPage < maxPages) { + currentPage += 1; + return true; + } + return false; + } + + public MessageEmbed createMessage(TranslationMap translations, CustomSubstitutions substitutions, + BiConsumer applier) { + if (currentPage != lastPage) { + cachedMessage = message.create( + translations, + substitutions.with(new HashMap<>()) + .with("page.max", () -> String.valueOf(maxPages + 1)) + .with("page.current", () -> String.valueOf(currentPage + 1)), + list.stream() + .skip(currentPage * amountPerPage) + .limit(amountPerPage) + .collect(Collectors.toList()), + applier); + lastPage = currentPage; + } + return cachedMessage.build(); + } + } + + enum PageDirection { + PREVIOUS, NEXT + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/MessageHelper.java b/src/main/java/sciwhiz12/janitor/msg/MessageHelper.java new file mode 100644 index 0000000..2a54033 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/MessageHelper.java @@ -0,0 +1,111 @@ +package sciwhiz12.janitor.msg; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.IMentionable; +import net.dv8tion.jda.api.entities.ISnowflake; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; +import sciwhiz12.janitor.moderation.notes.NoteEntry; +import sciwhiz12.janitor.moderation.warns.WarningEntry; +import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions; + +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.function.Consumer; + +import static java.time.temporal.ChronoField.*; + +public class MessageHelper { + private MessageHelper() {} + + public static > Consumer snowflake(String head, ISnowflake snowflake) { + return builder -> builder + .with(head + ".id", snowflake::getId) + .with(head + ".creation_datetime", () -> snowflake.getTimeCreated().format(DATE_TIME_FORMAT)); + } + + public static > Consumer mentionable(String head, IMentionable mentionable) { + return builder -> builder + .apply(snowflake(head, mentionable)) + .with(head + ".mention", mentionable::getAsMention); + } + + public static > Consumer role(String head, Role role) { + return builder -> builder + .apply(mentionable(head, role)) + .with(head + ".color_hex", () -> Integer.toHexString(role.getColorRaw())) + .with(head + ".name", role::getName) + .with(head + ".permissions", role.getPermissions()::toString); + } + + public static > Consumer user(String head, User user) { + return builder -> builder + .apply(mentionable(head, user)) + .with(head + ".name", user::getName) + .with(head + ".discriminator", user::getDiscriminator) + .with(head + ".tag", user::getAsTag) + .with(head + ".flags", user.getFlags()::toString); + } + + public static > Consumer guild(String head, Guild guild) { + return builder -> builder + .apply(snowflake(head, guild)) + .with(head + ".name", guild::getName) + .with(head + ".description", guild::getDescription) + .with(head + ".voice_region", guild.getRegion()::toString) + .with(head + ".boost.tier", guild.getBoostTier()::toString) + .with(head + ".boost.count", () -> String.valueOf(guild.getBoostCount())) + .with(head + ".locale", guild.getLocale()::toString) + .with(head + ".verification_level", guild.getVerificationLevel()::toString) + .with(head + ".icon_url", guild::getIconUrl); + } + + public static > Consumer member(String head, Member member) { + return builder -> builder + .apply(user(head, member.getUser())) + .apply(guild(head + ".guild", member.getGuild())) + .with(head + ".nickname", member::getNickname) + .with(head + ".effective_name", member::getEffectiveName) + .with(head + ".join_datetime", () -> member.getTimeJoined().format(DATE_TIME_FORMAT)) + .with(head + ".color", () -> String.valueOf(member.getColorRaw())); + } + + public static > Consumer warningEntry(String head, int caseID, WarningEntry entry) { + return builder -> builder + .with(head + ".case_id", () -> String.valueOf(caseID)) + .apply(user(head + ".performer", entry.getPerformer())) + .apply(user(head + ".target", entry.getWarned())) + .with(head + ".date_time", () -> entry.getDateTime().format(DATE_TIME_FORMAT)) + .with(head + ".reason", entry::getReason); + } + + public static > Consumer noteEntry(String head, int noteID, NoteEntry entry) { + return builder -> builder + .with(head + ".note_id", () -> String.valueOf(noteID)) + .apply(user(head + ".performer", entry.getPerformer())) + .apply(user(head + ".target", entry.getTarget())) + .with(head + ".date_time", () -> entry.getDateTime().format(DATE_TIME_FORMAT)) + .with(head + ".contents", entry::getContents); + } + + public static final DateTimeFormatter DATE_TIME_FORMAT = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .parseLenient() + .appendValue(YEAR, 4) // 2 digit year not handled + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalEnd() + .appendLiteral(' ') + .appendOffset("+HHMM", "GMT") + .toFormatter(); +} diff --git a/src/main/java/sciwhiz12/janitor/msg/Messages.java b/src/main/java/sciwhiz12/janitor/msg/Messages.java index 9b509d1..cfbde61 100644 --- a/src/main/java/sciwhiz12/janitor/msg/Messages.java +++ b/src/main/java/sciwhiz12/janitor/msg/Messages.java @@ -1,32 +1,161 @@ package sciwhiz12.janitor.msg; -import net.dv8tion.jda.api.EmbedBuilder; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.dv8tion.jda.api.entities.MessageEmbed; import sciwhiz12.janitor.JanitorBot; +import sciwhiz12.janitor.msg.json.ListingMessage; +import sciwhiz12.janitor.msg.json.RegularMessage; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.google.common.io.Resources.getResource; +import static sciwhiz12.janitor.Logging.JANITOR; +import static sciwhiz12.janitor.Logging.MESSAGES; public class Messages { - public static final int FAILURE_COLOR = 0xF73132; + public static final String JSON_FILE_SUFFIX = ".json"; + public static final String MESSAGES_FILENAME = "messages"; + public static final String DEFAULT_MESSAGES_FOLDER = "messages/"; + public static final TypeReference> LIST_TYPE = new TypeReference<>() {}; private final JanitorBot bot; - public final General GENERAL; - public final Moderation MODERATION; + private final Path messagesFolder; + private final Map regularMessages = new HashMap<>(); + private final Map listingMessages = new HashMap<>(); + private final ObjectMapper jsonMapper = new ObjectMapper(); - public Messages(JanitorBot bot) { + public Messages(JanitorBot bot, Path messagesFolder) { this.bot = bot; - this.GENERAL = new General(this); - this.MODERATION = new Moderation(this); + this.messagesFolder = messagesFolder; + loadMessages(); } - public String translate(String key, Object... args) { - return bot.getTranslations().translate(key, args); + public JanitorBot getBot() { + return bot; } - public EmbedBuilder failureEmbed(String title) { - return new EmbedBuilder() - .setTitle(title) - .setColor(FAILURE_COLOR) - .setTimestamp(OffsetDateTime.now(ZoneOffset.UTC)); + public void loadMessages() { + boolean success = false; + + if (messagesFolder != null) { + JANITOR.debug(MESSAGES, "Loading messages from folder {}", messagesFolder); + success = loadMessages( + path -> Files.newBufferedReader(messagesFolder.resolve(path + JSON_FILE_SUFFIX)) + ); + } else { + JANITOR.info(MESSAGES, "No custom messages folder specified"); + } + + if (!success) { + JANITOR.info(MESSAGES, "Loading default messages"); + //noinspection UnstableApiUsage + loadMessages( + file -> new InputStreamReader(getResource(DEFAULT_MESSAGES_FOLDER + file + JSON_FILE_SUFFIX).openStream()) + ); + } } + + boolean loadMessages(FileOpener files) { + try (Reader keyReader = files.open(MESSAGES_FILENAME)) { + List keysList = jsonMapper.readValue(keyReader, LIST_TYPE); + regularMessages.clear(); + for (String messageKey : keysList) { + final String path = messageKey.replace("/", FileSystems.getDefault().getSeparator()); + try (Reader reader = files.open(path)) { + final JsonNode tree = jsonMapper.readTree(reader); + final String type = tree.path("type").asText("regular"); + if ("regular".equals(type)) { + regularMessages.put(messageKey, jsonMapper.convertValue(tree, RegularMessage.class)); + } else if ("listing".equals(type)) { + listingMessages.put(messageKey, jsonMapper.convertValue(tree, ListingMessage.class)); + } else { + JANITOR.warn(MESSAGES, "Unknown message type {} for {}", tree.path("type").asText(), messageKey); + } + } catch (Exception e) { + JANITOR.error(MESSAGES, "Error while loading message {}", path, e); + } + } + JANITOR.info(MESSAGES, "Loaded {} messages", regularMessages.size()); + return true; + } catch (Exception e) { + JANITOR.error(MESSAGES, "Error while loading messages", e); + return false; + } + } + + public Map getRegularMessages() { + return Collections.unmodifiableMap(regularMessages); + } + + public RegularMessageBuilder getRegularMessage(String messageKey) { + final RegularMessage msg = regularMessages.get(messageKey); + if (msg == null) { + JANITOR.warn(MESSAGES, "Attempted to get unknown regular message with key {}", messageKey); + return new RegularMessageBuilder(UNKNOWN_REGULAR_MESSAGE).with("key", () -> messageKey); + } + return new RegularMessageBuilder(msg); + } + + public Map getListingMessages() { + return listingMessages; + } + + public ListingMessageBuilder getListingMessage(String messageKey) { + final ListingMessage msg = listingMessages.get(messageKey); + if (msg == null) { + JANITOR.warn(MESSAGES, "Attempted to get unknown listing message with key {}", messageKey); + return new ListingMessageBuilder(UNKNOWN_LISTING_MESSAGE).with("key", () -> messageKey); + } + return new ListingMessageBuilder<>(msg); + } + + interface FileOpener { + Reader open(String filePath) throws IOException; + } + + public static final RegularMessage UNKNOWN_REGULAR_MESSAGE = new RegularMessage( + "UNKNOWN MESSAGE!", + null, + "A regular message was tried to be looked up, but was not found. Please report this to your bot " + + "maintainer/administrator.", + String.valueOf(0xFF0000), + null, + null, + null, + null, + null, + null, + null, + Collections.singletonList(new MessageEmbed.Field("Message Key", "${key}", false)) + ); + + public static final ListingMessage UNKNOWN_LISTING_MESSAGE = new ListingMessage( + "UNKNOWN MESSAGE!", + null, + "A listing message was tried to be looked up, but was not found. " + + "Please report this to your bot maintainer/administrator.", + String.valueOf(0xFF0000), + null, + null, + null, + null, + null, + null, + null, + null, + new ListingMessage.DescriptionEntry(null, ""), + Collections.singletonList(new MessageEmbed.Field("Message Key", "${key}", false)), + Collections.emptyList() + ); } diff --git a/src/main/java/sciwhiz12/janitor/msg/Moderation.java b/src/main/java/sciwhiz12/janitor/msg/Moderation.java deleted file mode 100644 index f903876..0000000 --- a/src/main/java/sciwhiz12/janitor/msg/Moderation.java +++ /dev/null @@ -1,318 +0,0 @@ -package sciwhiz12.janitor.msg; - -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageChannel; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.requests.restaction.MessageAction; -import org.checkerframework.checker.nullness.qual.Nullable; -import sciwhiz12.janitor.moderation.notes.NoteEntry; -import sciwhiz12.janitor.moderation.warns.WarningEntry; - -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.Map; -import java.util.stream.Collectors; - -import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME; - -public final class Moderation { - public static final int MODERATION_COLOR = 0xF1BD25; - public static final String GAVEL_ICON_URL = "https://cdn.discordapp.com/attachments/738478941760782526" + - "/760463743330549760/gavel.png"; - - private final Messages messages; - public final Errors ERRORS; - - Moderation(Messages messages) { - this.messages = messages; - ERRORS = new Errors(); - } - - private String translate(String key, Object... args) { - return messages.translate(key, args); - } - - public EmbedBuilder moderationEmbed() { - return new EmbedBuilder() - .setColor(MODERATION_COLOR) - .setTimestamp(OffsetDateTime.now(ZoneOffset.UTC)); - } - - public EmbedBuilder moderationEmbed(String author) { - return moderationEmbed() - .setAuthor(author, null, GAVEL_ICON_URL); - } - - public class Errors { - private Errors() {} - - public MessageAction performerInsufficientPermissions(MessageChannel channel, Member performer, - EnumSet permissions) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.insufficient_permissions.title")) - .setDescription(translate("moderation.insufficient_permissions.desc")) - .addField( - translate("moderation.insufficient_permissions.field.performer"), - performer.getAsMention(), - true) - .addField(new MessageEmbed.Field( - translate("moderation.insufficient_permissions.field.permissions"), - permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")), true)) - .build() - ); - } - - public MessageAction cannotModerate(MessageChannel channel, Member performer, Member target) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.cannot_interact.title")) - .setDescription(translate("moderation.cannot_interact.desc")) - .addField(translate("moderation.cannot_interact.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.cannot_interact.field.target"), target.getAsMention(), true) - .build() - ); - } - - public MessageAction cannotWarnMods(MessageChannel channel, Member performer, Member target) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.warn.cannot_warn_mods.title")) - .setDescription(translate("moderation.warn.cannot_warn_mods.desc")) - .addField(translate("moderation.warn.cannot_warn_mods.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.warn.cannot_warn_mods.field.target"), target.getAsMention(), true).build() - ); - } - - public MessageAction cannotRemoveHigherModerated(MessageChannel channel, Member performer, int caseID, - WarningEntry entry) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.unwarn.cannot_remove_higher_mod.title")) - .setDescription(translate("moderation.unwarn.cannot_remove_higher_mod.desc")) - .addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.performer"), performer.getAsMention(), - true) - .addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.original_performer"), - entry.getPerformer().getAsMention(), true) - .addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.case_id"), String.valueOf(caseID), - true) - .build() - ); - } - - public MessageAction maxAmountOfNotes(MessageChannel channel, Member performer, Member target, int amount) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.note.max_amount_of_notes.title")) - .setDescription(translate("moderation.note.max_amount_of_notes.desc")) - .addField(translate("moderation.note.max_amount_of_notes.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.note.max_amount_of_notes.field.target"), target.getAsMention(), true) - .addField(translate("moderation.note.max_amount_of_notes.field.amount"), String.valueOf(amount), true) - .build() - ); - } - - public MessageAction noNoteFound(MessageChannel channel, Member performer, int noteID) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.note.no_note_found.title")) - .setDescription(translate("moderation.note.no_note_found.desc")) - .addField(translate("moderation.note.no_note_found.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.note.no_note_found.field.note_id"), String.valueOf(noteID), true) - .build() - ); - } - - public MessageAction noWarnWithID(MessageChannel channel, Member performer, int caseID) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.unwarn.no_case_found.title")) - .setDescription(translate("moderation.unwarn.no_case_found.desc")) - .addField(translate("moderation.unwarn.no_case_found.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.unwarn.no_case_found.field.case_id"), String.valueOf(caseID), true).build() - ); - } - - public MessageAction cannotUnwarnSelf(MessageChannel channel, Member performer, int caseID, WarningEntry entry) { - return channel.sendMessage( - messages.failureEmbed(translate("moderation.unwarn.cannot_unwarn_self.title")) - .setDescription(translate("moderation.unwarn.cannot_unwarn_self.desc")) - .addField(translate("moderation.unwarn.cannot_unwarn_self.field.performer"), performer.getAsMention(), - true) - .addField(translate("moderation.unwarn.cannot_unwarn_self.field.original_performer"), - entry.getPerformer().getAsMention(), true) - .addField(translate("moderation.unwarn.cannot_unwarn_self.field.case_id"), String.valueOf(caseID), true) - .build() - ); - } - } - - public MessageAction kickUser(MessageChannel channel, Member performer, Member target, @Nullable String reason, - boolean sentDM) { - return channel.sendMessage( - moderationEmbed(translate("moderation.kick.info.author")) - .addField(translate("moderation.kick.info.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.kick.info.field.target"), target.getAsMention(), true) - .addField(translate("moderation.kick.info.field.sent_private_message"), sentDM ? "✅" : "❌", true) - .addField(reason != null ? translate("moderation.kick.info.field.reason") : null, reason, false) - .build() - ); - } - - public MessageAction kickedDM(MessageChannel channel, Member performer, Member target, @Nullable String reason) { - return channel.sendMessage( - moderationEmbed() - .setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl()) - .setTitle(translate("moderation.kick.dm.title")) - .addField(translate("moderation.kick.dm.field.performer"), performer.getUser().getAsMention(), true) - .addField(reason != null ? translate("moderation.kick.dm.field.reason") : null, reason, false) - .build() - ); - } - - public MessageAction banUser(MessageChannel channel, Member performer, Member target, @Nullable String reason, - int deletionDays, boolean sentDM) { - return channel.sendMessage( - moderationEmbed(translate("moderation.ban.info.author")) - .addField(translate("moderation.ban.info.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.ban.info.field.target"), target.getAsMention(), true) - .addField(translate("moderation.ban.info.field.sent_private_message"), sentDM ? "✅" : "❌", true) - .addField(deletionDays != 0 ? - new MessageEmbed.Field(translate("moderation.ban.info.field.delete_duration"), - translate("moderation.ban.info.field.delete_duration.value", String.valueOf(deletionDays)), true) - : null) - .addField(reason != null ? translate("moderation.ban.info.field.reason") : null, reason, false) - .build() - ); - } - - public MessageAction bannedDM(MessageChannel channel, Member performer, @Nullable String reason) { - return channel.sendMessage( - moderationEmbed() - .setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl()) - .setTitle(translate("moderation.ban.dm.title")) - .addField(translate("moderation.ban.dm.field.performer"), performer.getAsMention(), true) - .addField(reason != null ? translate("moderation.ban.dm.field.reason") : null, reason, false) - .build() - ); - } - - public MessageAction unbanUser(MessageChannel channel, Member performer, User target) { - return channel.sendMessage( - moderationEmbed(translate("moderation.unban.info.author")) - .addField(translate("moderation.unban.info.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.unban.info.field.target"), target.getAsMention(), true) - .build() - ); - } - - public MessageAction warnUser(MessageChannel channel, Member performer, Member target, String reason, - OffsetDateTime dateTime, int caseID, boolean sentDM) { - return channel.sendMessage( - moderationEmbed(translate("moderation.warn.info.author")) - .addField(translate("moderation.warn.info.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.warn.info.field.target"), target.getAsMention(), true) - .addField(translate("moderation.warn.info.field.sent_private_message"), sentDM ? "✅" : "❌", true) - .addField(translate("moderation.warn.info.field.case_id"), String.valueOf(caseID), true) - .addField(translate("moderation.warn.info.field.date_time"), - dateTime.format(RFC_1123_DATE_TIME), true) - .addField(translate("moderation.warn.info.field.reason"), reason, false).build() - ); - } - - public MessageAction warnDM(MessageChannel channel, Member performer, Member target, String reason, - OffsetDateTime dateTime) { - return channel.sendMessage( - moderationEmbed() - .setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl()) - .setTitle(translate("moderation.warn.dm.title")) - .addField(translate("moderation.warn.dm.field.performer"), performer.getUser().getAsMention(), true) - .addField(translate("moderation.warn.dm.field.date_time"), - dateTime.format(RFC_1123_DATE_TIME), true) - .addField(translate("moderation.warn.dm.field.reason"), reason, false) - .build() - ); - } - - public MessageAction warnList(MessageChannel channel, Map displayWarnings) { - return channel.sendMessage( - moderationEmbed(translate("moderation.warnlist.author")) - .setDescription(displayWarnings.size() > 0 ? displayWarnings.entrySet().stream() - .sorted(Collections.reverseOrder(Comparator.comparingInt(Map.Entry::getKey))) - .limit(10) - .map(entry -> - translate("moderation.warnlist.entry", - entry.getKey(), - entry.getValue().getWarned().getAsMention(), - entry.getValue().getPerformer().getAsMention(), - entry.getValue().getDateTime().format(RFC_1123_DATE_TIME), - entry.getValue().getReason() != null - ? entry.getValue().getReason() - : translate("moderation.warnlist.entry.no_reason")) - ) - .collect(Collectors.joining("\n")) - : translate("moderation.warnlist.empty")) - .build() - ); - } - - public MessageAction unwarn(MessageChannel channel, Member performer, int caseID, WarningEntry entry) { - return channel.sendMessage( - moderationEmbed(translate("moderation.unwarn.author")) - .addField(translate("moderation.unwarn.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.unwarn.field.case_id"), String.valueOf(caseID), true) - .addField(translate("moderation.unwarn.field.original_target"), entry.getWarned().getAsMention(), true) - .addField(translate("moderation.unwarn.field.original_performer"), entry.getPerformer().getAsMention(), true) - .addField(translate("moderation.unwarn.field.date_time"), entry.getDateTime().format(RFC_1123_DATE_TIME), true) - .addField(entry.getReason() != null ? translate("moderation.unwarn.field.reason") : null, entry.getReason(), - false) - .build() - ); - } - - public MessageAction addNote(MessageChannel channel, Member performer, Member target, String contents, - OffsetDateTime dateTime, int noteID) { - return channel.sendMessage( - moderationEmbed(translate("moderation.note.add.author")) - .addField(translate("moderation.note.add.field.performer"), performer.getUser().getAsMention(), true) - .addField(translate("moderation.note.add.field.target"), target.getUser().getAsMention(), true) - .addField(translate("moderation.note.add.field.note_id"), String.valueOf(noteID), true) - .addField(translate("moderation.note.add.field.date_time"), dateTime.format(RFC_1123_DATE_TIME), true) - .addField(translate("moderation.note.add.field.contents"), contents, false) - .build() - ); - } - - public MessageAction noteList(MessageChannel channel, Map displayNotes) { - return channel.sendMessage(moderationEmbed(translate("moderation.note.list.author")) - .setDescription(displayNotes.size() > 0 ? displayNotes.entrySet().stream() - .sorted(Collections.reverseOrder(Comparator.comparingInt(Map.Entry::getKey))) - .limit(10) - .map(entry -> - translate("moderation.note.list.entry", - entry.getKey(), - entry.getValue().getTarget().getAsMention(), - entry.getValue().getPerformer().getAsMention(), - entry.getValue().getDateTime().format(RFC_1123_DATE_TIME), - entry.getValue().getContents()) - ) - .collect(Collectors.joining("\n")) - : translate("moderation.note.list.empty")) - .build() - ); - } - - public MessageAction removeNote(MessageChannel channel, Member performer, int noteID, NoteEntry entry) { - return channel.sendMessage( - moderationEmbed(translate("moderation.note.remove.author")) - .addField(translate("moderation.note.remove.field.performer"), performer.getAsMention(), true) - .addField(translate("moderation.note.remove.field.note_id"), String.valueOf(noteID), true) - .addField(translate("moderation.note.remove.field.original_target"), entry.getTarget().getAsMention(), true) - .addField(translate("moderation.note.remove.field.original_performer"), entry.getPerformer().getAsMention(), - true) - .addField(translate("moderation.note.remove.field.date_time"), entry.getDateTime().format(RFC_1123_DATE_TIME), - true) - .addField(translate("moderation.note.remove.field.contents"), entry.getContents(), false) - .build() - ); - } -} diff --git a/src/main/java/sciwhiz12/janitor/msg/RegularMessageBuilder.java b/src/main/java/sciwhiz12/janitor/msg/RegularMessageBuilder.java new file mode 100644 index 0000000..37cad71 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/RegularMessageBuilder.java @@ -0,0 +1,54 @@ +package sciwhiz12.janitor.msg; + +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.requests.restaction.MessageAction; +import sciwhiz12.janitor.JanitorBot; +import sciwhiz12.janitor.msg.json.RegularMessage; +import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions; +import sciwhiz12.janitor.msg.substitution.SubstitutionMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class RegularMessageBuilder implements IHasCustomSubstitutions { + private final RegularMessage message; + private final Map> customSubstitutions; + + public RegularMessageBuilder(RegularMessage message, Map> customSubstitutions) { + this.message = message; + this.customSubstitutions = customSubstitutions; + } + + public RegularMessageBuilder(RegularMessage message) { + this(message, new HashMap<>()); + } + + public RegularMessageBuilder(RegularMessageBuilder copy) { + this(copy.message, new HashMap<>(copy.customSubstitutions)); + } + + public RegularMessageBuilder apply(Consumer consumer) { + consumer.accept(this); + return this; + } + + public RegularMessageBuilder with(final String argument, final Supplier value) { + customSubstitutions.put(argument, value); + return this; + } + + public MessageEmbed build(TranslationMap translations, SubstitutionMap substitutions) { + return message.create(translations, substitutions.with(customSubstitutions)).build(); + } + + public MessageEmbed build(JanitorBot bot) { + return build(bot.getTranslations(), bot.getSubstitutions()); + } + + public MessageAction send(JanitorBot bot, MessageChannel channel) { + return channel.sendMessage(build(bot)); + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/Translations.java b/src/main/java/sciwhiz12/janitor/msg/TranslationMap.java similarity index 64% rename from src/main/java/sciwhiz12/janitor/msg/Translations.java rename to src/main/java/sciwhiz12/janitor/msg/TranslationMap.java index 5dda90d..543488a 100644 --- a/src/main/java/sciwhiz12/janitor/msg/Translations.java +++ b/src/main/java/sciwhiz12/janitor/msg/TranslationMap.java @@ -1,32 +1,35 @@ package sciwhiz12.janitor.msg; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.Resources; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; import sciwhiz12.janitor.JanitorBot; import java.io.InputStreamReader; -import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static java.util.regex.Matcher.quoteReplacement; +import static java.util.regex.Pattern.CASE_INSENSITIVE; import static sciwhiz12.janitor.Logging.JANITOR; import static sciwhiz12.janitor.Logging.TRANSLATIONS; -public class Translations { - private static final Gson GSON = new GsonBuilder().create(); +public class TranslationMap { + public static final Pattern TRANSLATION_REGEX = Pattern.compile("<(.+?)>", CASE_INSENSITIVE); private static final String DEFAULT_TRANSLATIONS_RESOURCE = "english.json"; - private static final Type MAP_TYPE = new TypeToken>() {}.getType(); + private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; private final JanitorBot bot; private final Path translationsFile; private final Map translations = new HashMap<>(); + private final ObjectMapper jsonMapper = new ObjectMapper(); - public Translations(JanitorBot bot, Path translationsFile) { + public TranslationMap(JanitorBot bot, Path translationsFile) { this.bot = bot; this.translationsFile = translationsFile; loadTranslations(); @@ -40,12 +43,11 @@ public class Translations { } try { JANITOR.debug(TRANSLATIONS, "Loading translations from file {}", translationsFile); - Map trans = GSON.fromJson(Files.newBufferedReader(translationsFile), MAP_TYPE); + Map trans = jsonMapper.readValue(Files.newBufferedReader(translationsFile), MAP_TYPE); translations.clear(); translations.putAll(trans); JANITOR.info(TRANSLATIONS, "Loaded {} translations from file {}", translations.size(), translationsFile); - } - catch (Exception e) { + } catch (Exception e) { JANITOR.error(TRANSLATIONS, "Error while loading translations from file {}", translationsFile, e); loadDefaultTranslations(); } @@ -55,14 +57,13 @@ public class Translations { try { JANITOR.debug(TRANSLATIONS, "Loading default english translations"); // noinspection UnstableApiUsage - Map trans = GSON.fromJson( + Map trans = jsonMapper.readValue( new InputStreamReader(Resources.getResource(DEFAULT_TRANSLATIONS_RESOURCE).openStream()), MAP_TYPE); translations.clear(); translations.putAll(trans); JANITOR.info(TRANSLATIONS, "Loaded {} default english translations", translations.size()); - } - catch (Exception e) { + } catch (Exception e) { JANITOR.error(TRANSLATIONS, "Error while loading default english translations", e); } } @@ -71,7 +72,13 @@ public class Translations { return Collections.unmodifiableMap(translations); } - public String translate(String key, Object... args) { - return String.format(translations.getOrDefault(key, key), args); + public String translate(String text) { + final Matcher matcher = TRANSLATION_REGEX.matcher(text); + return matcher.replaceAll( + matchResult -> quoteReplacement(translations.getOrDefault(matchResult.group(1), matchResult.group(0)))); + } + + public JanitorBot getBot() { + return bot; } } diff --git a/src/main/java/sciwhiz12/janitor/msg/emote/ReactionManager.java b/src/main/java/sciwhiz12/janitor/msg/emote/ReactionManager.java new file mode 100644 index 0000000..17204a8 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/emote/ReactionManager.java @@ -0,0 +1,43 @@ +package sciwhiz12.janitor.msg.emote; + +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.events.message.MessageDeleteEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import sciwhiz12.janitor.JanitorBot; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; + +public class ReactionManager extends ListenerAdapter { + private final JanitorBot bot; + private final Map messageMap = new HashMap<>(); + + public ReactionManager(JanitorBot bot) { + this.bot = bot; + } + + public ReactionMessage newMessage(Message message) { + if (messageMap.containsKey(message.getIdLong())) { + throw new IllegalArgumentException("Reaction message already exists for message with id " + message.getIdLong()); + } + final ReactionMessage msg = new ReactionMessage(bot, message); + messageMap.put(message.getIdLong(), msg); + return msg; + } + + public void removeMessage(long messageID) { + bot.getDiscord().removeEventListener(messageMap.remove(messageID)); + } + + public Map getRegisteredMessages() { + return messageMap; + } + + @Override + public void onMessageDelete(@Nonnull MessageDeleteEvent event) { + if (messageMap.containsKey(event.getMessageIdLong())) { + bot.getDiscord().removeEventListener(messageMap.get(event.getMessageIdLong())); + } + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/emote/ReactionMessage.java b/src/main/java/sciwhiz12/janitor/msg/emote/ReactionMessage.java new file mode 100644 index 0000000..6d4e924 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/emote/ReactionMessage.java @@ -0,0 +1,112 @@ +package sciwhiz12.janitor.msg.emote; + +import net.dv8tion.jda.api.entities.Emote; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageReaction.ReactionEmote; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.exceptions.ErrorHandler; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.ErrorResponse; +import sciwhiz12.janitor.JanitorBot; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import javax.annotation.Nonnull; + +import static net.dv8tion.jda.api.Permission.MESSAGE_MANAGE; + +public class ReactionMessage extends ListenerAdapter { + private final JanitorBot bot; + private final Message message; + private final Map emotes = new LinkedHashMap<>(); + private boolean removeEmotes = true; + private long ownerID; + private boolean onlyOwner; + + public ReactionMessage(JanitorBot bot, Message message, boolean onlyOwner, long ownerID) { + this.bot = bot; + this.message = message; + this.ownerID = ownerID; + this.onlyOwner = onlyOwner; + } + + public ReactionMessage(JanitorBot bot, Message message) { + this(bot, message, false, 0); + } + + public ReactionMessage add(ReactionEmote emote, IReactionListener listener) { + emotes.put(emote, listener); + return this; + } + + public ReactionMessage add(String emote, IReactionListener listener) { + return add(ReactionEmote.fromUnicode(emote, bot.getDiscord()), listener); + } + + public ReactionMessage add(Emote emote, IReactionListener listener) { + return add(ReactionEmote.fromCustom(emote), listener); + } + + public ReactionMessage removeEmotes(boolean remove) { + this.removeEmotes = remove; + return this; + } + + public ReactionMessage owner(long ownerID) { + this.ownerID = ownerID; + this.onlyOwner = true; + return this; + } + + public void create() { + for (ReactionEmote reaction : emotes.keySet()) { + if (reaction.isEmote()) { + message.addReaction(reaction.getEmote()).queue(); + } else { + message.addReaction(reaction.getEmoji()).queue(); + } + } + bot.getDiscord().addEventListener(this); + } + + @Override + public void onMessageReactionAdd(@Nonnull MessageReactionAddEvent event) { + if (event.getMessageIdLong() != message.getIdLong()) return; + if (event.getUserIdLong() == bot.getDiscord().getSelfUser().getIdLong()) return; + if (onlyOwner && event.getUserIdLong() != ownerID) return; + + emotes.keySet().stream() + .filter(emote -> event.getReactionEmote().equals(emote)) + .forEach(emote -> emotes.get(emote).accept(this, event)); + + if (removeEmotes && (!event.isFromGuild() + || event.getGuild().getSelfMember().hasPermission(event.getTextChannel(), MESSAGE_MANAGE))) { + event.retrieveUser() + .flatMap(user -> event.getReaction().removeReaction(user)) + .queue(null, new ErrorHandler().ignore(ErrorResponse.UNKNOWN_MESSAGE)); + } + } + + public JanitorBot getBot() { + return bot; + } + + public long getOwnerID() { + return ownerID; + } + + public boolean isOwnerOnly() { + return onlyOwner; + } + + public Map getListeners() { + return Collections.unmodifiableMap(emotes); + } + + @FunctionalInterface + public interface IReactionListener extends BiConsumer { + void accept(ReactionMessage message, MessageReactionAddEvent event); + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/json/ListingMessage.java b/src/main/java/sciwhiz12/janitor/msg/json/ListingMessage.java new file mode 100644 index 0000000..50365f2 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/json/ListingMessage.java @@ -0,0 +1,254 @@ +package sciwhiz12.janitor.msg.json; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.primitives.Ints; +import joptsimple.internal.Strings; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import sciwhiz12.janitor.msg.TranslationMap; +import sciwhiz12.janitor.msg.substitution.CustomSubstitutions; +import sciwhiz12.janitor.msg.substitution.ISubstitutor; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; +import javax.annotation.Nullable; + +@JsonDeserialize(using = ListingMessageDeserializer.class) +public class ListingMessage { + @Nullable protected final String title; + @Nullable protected final String url; + @Nullable protected final String description; + @Nullable protected final String color; + @Nullable protected final String authorName; + @Nullable protected final String authorUrl; + @Nullable protected final String authorIconUrl; + @Nullable protected final String footerText; + @Nullable protected final String footerIconUrl; + @Nullable protected final String imageUrl; + @Nullable protected final String thumbnailUrl; + @Nullable protected final String emptyText; + protected final Entry entry; + protected final List beforeFields; + protected final List afterFields; + + public ListingMessage( + @Nullable String title, + @Nullable String url, + @Nullable String description, + @Nullable String color, + @Nullable String authorName, + @Nullable String authorUrl, + @Nullable String authorIconUrl, + @Nullable String footerText, + @Nullable String footerIconUrl, + @Nullable String imageUrl, + @Nullable String thumbnailUrl, + @Nullable String emptyText, + Entry entry, + List beforeFields, + List afterFields + ) { + this.title = title; + this.url = url; + this.description = description; + this.color = color; + this.authorName = authorName; + this.authorUrl = authorUrl; + this.authorIconUrl = authorIconUrl; + this.footerText = footerText; + this.footerIconUrl = footerIconUrl; + this.imageUrl = imageUrl; + this.thumbnailUrl = thumbnailUrl; + this.emptyText = emptyText; + this.entry = entry; + this.beforeFields = beforeFields; + this.afterFields = afterFields; + } + + @Nullable + public String getTitle() { + return title; + } + + @Nullable + public String getUrl() { + return url; + } + + @Nullable + public String getDescription() { + return description; + } + + @Nullable + public String getColor() { + return color; + } + + @Nullable + public String getAuthorName() { + return authorName; + } + + @Nullable + public String getAuthorUrl() { + return authorUrl; + } + + @Nullable + public String getAuthorIconUrl() { + return authorIconUrl; + } + + @Nullable + public String getFooterText() { + return footerText; + } + + @Nullable + public String getFooterIconUrl() { + return footerIconUrl; + } + + @Nullable + public String getImageUrl() { + return imageUrl; + } + + @Nullable + public String getThumbnailUrl() { + return thumbnailUrl; + } + + @Nullable + public String getEmptyText() { + return emptyText; + } + + public Entry getEntry() { + return entry; + } + + public List getBeforeFields() { + return beforeFields; + } + + public List getAfterFields() { + return afterFields; + } + + public EmbedBuilder create( + TranslationMap translations, + ISubstitutor global, + Iterable iterable, + BiConsumer entryApplier + ) { + final Function func = str -> str != null ? global.substitute(translations.translate(str)) : null; + final EmbedBuilder builder = new EmbedBuilder(); + builder.setTitle(func.apply(title), func.apply(url)); + builder.setColor(parseColor(global.substitute(color))); + builder.setAuthor(func.apply(authorName), func.apply(authorUrl), func.apply(authorIconUrl)); + builder.setDescription(func.apply(description)); + builder.setImage(func.apply(imageUrl)); + builder.setThumbnail(func.apply(thumbnailUrl)); + builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC)); + builder.setFooter(func.apply(footerText), func.apply(footerIconUrl)); + for (MessageEmbed.Field field : beforeFields) { + builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline()); + } + + final CustomSubstitutions entrySubs = new CustomSubstitutions(); + final Function entryFunc = str -> str != null ? entrySubs.substitute(func.apply(str)) : null; + int count = 0; + for (T listEntry : iterable) { + entryApplier.accept(listEntry, entrySubs); + if (entry instanceof FieldEntry) { + FieldEntry fieldEntry = (FieldEntry) entry; + builder.addField( + entryFunc.apply(fieldEntry.getFieldName()), + entryFunc.apply(fieldEntry.getFieldValue()), + fieldEntry.isInline() + ); + } else if (entry instanceof DescriptionEntry) { + DescriptionEntry descEntry = (DescriptionEntry) entry; + builder.getDescriptionBuilder().append(entryFunc.apply(descEntry.getDescription())); + builder.getDescriptionBuilder().append(descEntry.getJoiner()); + } + count++; + } + if (count < 1) { + builder.getDescriptionBuilder().append(func.apply(emptyText)); + } + + for (MessageEmbed.Field field : afterFields) { + builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline()); + } + return builder; + } + + private static int parseColor(String str) { + if (Strings.isNullOrEmpty(str)) return Role.DEFAULT_COLOR_RAW; + if (str.startsWith("0x")) { + // noinspection UnstableApiUsage + final Integer res = Ints.tryParse(str.substring(2), 16); + if (res != null) { + return res; + } + } + // noinspection UnstableApiUsage + final Integer res = Ints.tryParse(str, 10); + if (res != null) { + return res; + } + return Role.DEFAULT_COLOR_RAW; + } + + public interface Entry {} + + public static class DescriptionEntry implements Entry { + public static final String DEFAULT_JOINER = "\n"; + private final String joiner; + private final String descriptionEntry; + + public DescriptionEntry(@Nullable String joiner, String descriptionEntry) { + this.joiner = joiner != null ? joiner : DEFAULT_JOINER; + this.descriptionEntry = descriptionEntry; + } + + public String getJoiner() { + return joiner; + } + + public String getDescription() { + return descriptionEntry; + } + } + + public static class FieldEntry implements Entry { + private final String fieldName; + private final String fieldValue; + private final boolean inline; + + public FieldEntry(String fieldName, String fieldValue, boolean inline) { + this.fieldName = fieldName; + this.fieldValue = fieldValue; + this.inline = inline; + } + + public String getFieldName() { + return fieldName; + } + + public String getFieldValue() { + return fieldValue; + } + + public boolean isInline() { + return inline; + } + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/json/ListingMessageDeserializer.java b/src/main/java/sciwhiz12/janitor/msg/json/ListingMessageDeserializer.java new file mode 100644 index 0000000..8e5c69b --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/json/ListingMessageDeserializer.java @@ -0,0 +1,117 @@ +package sciwhiz12.janitor.msg.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class ListingMessageDeserializer extends StdDeserializer { + public ListingMessageDeserializer() { + super(ListingMessage.class); + } + + @Override + public ListingMessage deserialize(JsonParser p, DeserializationContext ctx) + throws IOException { + + final JsonNode root = ctx.readTree(p); + + String title = null; + String url = null; + String description = root.path("description").asText(null); + String color = root.path("color").asText(null); + String authorName = null; + String authorUrl = null; + String authorIconUrl = null; + String footerText = null; + String footerIconUrl = null; + String imageUrl = root.path("image").asText(null); + String thumbnailUrl = root.path("thumbnail").asText(null); + String emptyText = root.path("empty").asText(null); + List beforeFields = readFields(root.path("fields").path("before")); + List afterFields = readFields(root.path("fields").path("after")); + + // Title + if (root.path("title").isTextual()) { + title = root.path("title").asText(); + } else if (root.path("title").path("text").isTextual()) { + title = root.path("title").path("text").asText(); + url = root.path("title").path("url").asText(null); + } + + // Author + if (root.path("author").isTextual()) { + authorName = root.path("author").asText(); + } else if (root.path("author").path("name").isTextual()) { + authorName = root.path("author").path("name").asText(); + authorUrl = root.path("author").path("url").asText(null); + authorIconUrl = root.path("author").path("icon_url").asText(null); + } + + // Footer + if (root.path("footer").isTextual()) { + footerText = root.path("footer").asText(); + } else if (root.path("footer").path("text").isTextual()) { + footerText = root.path("footer").path("text").asText(); + footerIconUrl = root.path("footer").path("icon_url").asText(null); + } + + // ENTRY + final ListingMessage.Entry entry = readEntry(root.path("entry")); + + return new ListingMessage(title, url, description, color, authorName, authorUrl, authorIconUrl, footerText, + footerIconUrl, imageUrl, thumbnailUrl, emptyText, entry, beforeFields, afterFields); + } + + public static ListingMessage.Entry readEntry(JsonNode root) { + switch (root.path("type").asText()) { + case "field": { + return new ListingMessage.FieldEntry( + root.path("name").asText(EmbedBuilder.ZERO_WIDTH_SPACE), + root.path("value").asText(EmbedBuilder.ZERO_WIDTH_SPACE), + root.path("inline").asBoolean(false) + ); + } + default: + case "description": { + return new ListingMessage.DescriptionEntry( + root.path("joiner").asText(null), + root.path("text").asText()); + } + } + } + + public static List readFields(JsonNode node) { + if (node.isArray()) { + final ArrayList fields = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + final MessageEmbed.Field field = readField(node.path(i)); + if (field != null) { + fields.add(field); + } + } + return fields; + } + return Collections.emptyList(); + } + + @Nullable + public static MessageEmbed.Field readField(JsonNode fieldNode) { + if (fieldNode.path("name").isTextual() && fieldNode.path("value").isTextual()) { + return new MessageEmbed.Field( + fieldNode.path("name").asText(), + fieldNode.path("value").asText(), + fieldNode.path("inline").asBoolean(false) + ); + } + return null; + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/json/RegularMessage.java b/src/main/java/sciwhiz12/janitor/msg/json/RegularMessage.java new file mode 100644 index 0000000..10472d6 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/json/RegularMessage.java @@ -0,0 +1,212 @@ +package sciwhiz12.janitor.msg.json; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.primitives.Ints; +import joptsimple.internal.Strings; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.Role; +import sciwhiz12.janitor.msg.TranslationMap; +import sciwhiz12.janitor.msg.substitution.ISubstitutor; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.function.Function; +import javax.annotation.Nullable; + +@JsonDeserialize(using = RegularMessageDeserializer.class) +public class RegularMessage { + @Nullable + protected final String title; + @Nullable + protected final String url; + @Nullable + protected final String description; + @Nullable + protected final String color; + @Nullable + protected final String authorName; + @Nullable + protected final String authorUrl; + @Nullable + protected final String authorIconUrl; + @Nullable + protected final String footerText; + @Nullable + protected final String footerIconUrl; + @Nullable + protected final String imageUrl; + @Nullable + protected final String thumbnailUrl; + protected final List fields; + + public RegularMessage( + @Nullable String title, + @Nullable String url, + @Nullable String description, + @Nullable String color, + @Nullable String authorName, + @Nullable String authorUrl, + @Nullable String authorIconUrl, + @Nullable String footerText, + @Nullable String footerIconUrl, + @Nullable String imageUrl, + @Nullable String thumbnailUrl, + List fields + ) { + this.title = title; + this.url = url; + this.description = description; + this.color = color; + this.authorName = authorName; + this.authorUrl = authorUrl; + this.authorIconUrl = authorIconUrl; + this.footerText = footerText; + this.footerIconUrl = footerIconUrl; + this.imageUrl = imageUrl; + this.thumbnailUrl = thumbnailUrl; + this.fields = new ArrayList<>(fields); + } + + @Nullable + public String getTitle() { + return title; + } + + @Nullable + public String getUrl() { + return url; + } + + @Nullable + public String getDescription() { + return description; + } + + @Nullable + public String getColor() { + return color; + } + + @Nullable + public String getAuthorName() { + return authorName; + } + + @Nullable + public String getAuthorUrl() { + return authorUrl; + } + + @Nullable + public String getAuthorIconUrl() { + return authorIconUrl; + } + + @Nullable + public String getFooterText() { + return footerText; + } + + @Nullable + public String getFooterIconUrl() { + return footerIconUrl; + } + + @Nullable + public String getImageUrl() { + return imageUrl; + } + + @Nullable + public String getThumbnailUrl() { + return thumbnailUrl; + } + + public List getFields() { + return Collections.unmodifiableList(fields); + } + + @Override + public String toString() { + return new StringJoiner(", ", RegularMessage.class.getSimpleName() + "[", "]") + .add("title='" + title + "'") + .add("url='" + url + "'") + .add("description='" + description + "'") + .add("color='" + color + "'") + .add("authorName='" + authorName + "'") + .add("authorUrl='" + authorUrl + "'") + .add("authorIconUrl='" + authorIconUrl + "'") + .add("footerText='" + footerText + "'") + .add("footerIconUrl='" + footerIconUrl + "'") + .add("imageUrl='" + imageUrl + "'") + .add("thumbnailUrl='" + thumbnailUrl + "'") + .add("fields=" + fields) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegularMessage that = (RegularMessage) o; + return Objects.equals(title, that.title) && + Objects.equals(url, that.url) && + Objects.equals(description, that.description) && + Objects.equals(color, that.color) && + Objects.equals(authorName, that.authorName) && + Objects.equals(authorUrl, that.authorUrl) && + Objects.equals(authorIconUrl, that.authorIconUrl) && + Objects.equals(footerText, that.footerText) && + Objects.equals(footerIconUrl, that.footerIconUrl) && + Objects.equals(imageUrl, that.imageUrl) && + Objects.equals(thumbnailUrl, that.thumbnailUrl) && + fields.equals(that.fields); + } + + @Override + public int hashCode() { + return Objects + .hash(title, url, description, color, authorName, authorUrl, authorIconUrl, footerText, footerIconUrl, imageUrl, + thumbnailUrl, fields); + } + + public EmbedBuilder create(TranslationMap translations, ISubstitutor substitutions) { + final Function func = str -> str != null ? substitutions.substitute(translations.translate(str)) : null; + final EmbedBuilder builder = new EmbedBuilder(); + builder.setTitle(func.apply(title), func.apply(url)); + builder.setColor(parseColor(substitutions.substitute(color))); + builder.setAuthor(func.apply(authorName), func.apply(authorUrl), func.apply(authorIconUrl)); + builder.setDescription(func.apply(description)); + builder.setImage(func.apply(imageUrl)); + builder.setThumbnail(func.apply(thumbnailUrl)); + builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC)); + builder.setFooter(func.apply(footerText), func.apply(footerIconUrl)); + for (MessageEmbed.Field field : fields) { + builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline()); + } + return builder; + } + + private static int parseColor(String str) { + if (Strings.isNullOrEmpty(str)) return Role.DEFAULT_COLOR_RAW; + if (str.startsWith("0x")) { + // noinspection UnstableApiUsage + final Integer res = Ints.tryParse(str.substring(2), 16); + if (res != null) { + return res; + } + } + // noinspection UnstableApiUsage + final Integer res = Ints.tryParse(str, 10); + if (res != null) { + return res; + } + return Role.DEFAULT_COLOR_RAW; + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/json/RegularMessageDeserializer.java b/src/main/java/sciwhiz12/janitor/msg/json/RegularMessageDeserializer.java new file mode 100644 index 0000000..36857e3 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/json/RegularMessageDeserializer.java @@ -0,0 +1,93 @@ +package sciwhiz12.janitor.msg.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class RegularMessageDeserializer extends StdDeserializer { + public RegularMessageDeserializer() { + super(RegularMessage.class); + } + + @Override + public RegularMessage deserialize(JsonParser p, DeserializationContext ctx) + throws IOException { + + final JsonNode node = ctx.readTree(p); + + String title = null; + String url = null; + String description = node.path("description").asText(null); + String color = node.path("color").asText(null); + String authorName = null; + String authorUrl = null; + String authorIconUrl = null; + String footerText = null; + String footerIconUrl = null; + String imageUrl = node.path("image").asText(null); + String thumbnailUrl = node.path("thumbnail").asText(null); + List fields = readFields(node); + + // Title + if (node.path("title").isTextual()) { + title = node.path("title").asText(); + } else if (node.path("title").path("text").isTextual()) { + title = node.path("title").path("text").asText(); + url = node.path("title").path("url").asText(null); + } + + // Author + if (node.path("author").isTextual()) { + authorName = node.path("author").asText(); + } else if (node.path("author").path("name").isTextual()) { + authorName = node.path("author").path("name").asText(); + authorUrl = node.path("author").path("url").asText(null); + authorIconUrl = node.path("author").path("icon_url").asText(null); + } + + // Footer + if (node.path("footer").isTextual()) { + footerText = node.path("footer").asText(); + } else if (node.path("footer").path("text").isTextual()) { + footerText = node.path("footer").path("text").asText(); + footerIconUrl = node.path("footer").path("icon_url").asText(null); + } + + return new RegularMessage(title, url, description, color, authorName, authorUrl, + authorIconUrl, footerText, footerIconUrl, imageUrl, thumbnailUrl, fields); + } + + public static List readFields(JsonNode node) { + if (node.path("fields").isArray()) { + final ArrayList fields = new ArrayList<>(); + for (int i = 0; i < node.path("fields").size(); i++) { + final MessageEmbed.Field field = readField(node.path("fields").path(i)); + if (field != null) { + fields.add(field); + } + } + return fields; + } + return Collections.emptyList(); + } + + @Nullable + public static MessageEmbed.Field readField(JsonNode fieldNode) { + if (fieldNode.path("name").isTextual() && fieldNode.path("value").isTextual()) { + return new MessageEmbed.Field( + fieldNode.path("name").asText(), + fieldNode.path("value").asText(), + fieldNode.path("inline").asBoolean(false) + ); + } + return null; + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/substitution/CustomSubstitutions.java b/src/main/java/sciwhiz12/janitor/msg/substitution/CustomSubstitutions.java new file mode 100644 index 0000000..bcbf9d0 --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/substitution/CustomSubstitutions.java @@ -0,0 +1,48 @@ +package sciwhiz12.janitor.msg.substitution; + +import org.apache.commons.collections4.TransformerUtils; +import org.apache.commons.collections4.map.DefaultedMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class CustomSubstitutions implements ISubstitutor, IHasCustomSubstitutions { + private final Map> map; + + public CustomSubstitutions(Map> map) { + this.map = map; + } + + public CustomSubstitutions() { + this(new HashMap<>()); + } + + @Override + public String substitute(String text) { + return SubstitutionMap.substitute(text, map); + } + + public CustomSubstitutions apply(Consumer consumer) { + consumer.accept(this); + return this; + } + + public CustomSubstitutions with(final String argument, final Supplier value) { + map.put(argument, value); + return this; + } + + public CustomSubstitutions with(Map> customSubstitutions) { + return new CustomSubstitutions(createDefaultedMap(customSubstitutions)); + } + + public Map> createDefaultedMap(Map> custom) { + return DefaultedMap.defaultedMap(custom, TransformerUtils.mapTransformer(map)); + } + + public Map> getMap() { + return map; + } +} diff --git a/src/main/java/sciwhiz12/janitor/msg/substitution/IHasCustomSubstitutions.java b/src/main/java/sciwhiz12/janitor/msg/substitution/IHasCustomSubstitutions.java new file mode 100644 index 0000000..93f772c --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/substitution/IHasCustomSubstitutions.java @@ -0,0 +1,10 @@ +package sciwhiz12.janitor.msg.substitution; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public interface IHasCustomSubstitutions> { + T with(String argument, Supplier value); + + T apply(Consumer consumer); +} diff --git a/src/main/java/sciwhiz12/janitor/msg/substitution/ISubstitutor.java b/src/main/java/sciwhiz12/janitor/msg/substitution/ISubstitutor.java new file mode 100644 index 0000000..907b51d --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/substitution/ISubstitutor.java @@ -0,0 +1,5 @@ +package sciwhiz12.janitor.msg.substitution; + +public interface ISubstitutor { + String substitute(String text); +} diff --git a/src/main/java/sciwhiz12/janitor/msg/substitution/SubstitutionMap.java b/src/main/java/sciwhiz12/janitor/msg/substitution/SubstitutionMap.java new file mode 100644 index 0000000..00c2d5d --- /dev/null +++ b/src/main/java/sciwhiz12/janitor/msg/substitution/SubstitutionMap.java @@ -0,0 +1,70 @@ +package sciwhiz12.janitor.msg.substitution; + +import org.apache.commons.collections4.TransformerUtils; +import org.apache.commons.collections4.map.DefaultedMap; +import sciwhiz12.janitor.JanitorBot; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Matcher.quoteReplacement; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT; + +public class SubstitutionMap implements ISubstitutor { + public static final Pattern ARGUMENT_REGEX = Pattern.compile("\\$\\{(.+?)}", CASE_INSENSITIVE); + public static final Pattern NULL_ARGUMENT_REGEX = Pattern.compile("nullcheck;(.+?);(.+)", CASE_INSENSITIVE); + + public static String substitute(String text, Map> arguments) { + final Matcher matcher = ARGUMENT_REGEX.matcher(text); + return matcher.replaceAll(matchResult -> { + final Matcher nullMatcher = NULL_ARGUMENT_REGEX.matcher(matchResult.group(1)); + if (nullMatcher.matches()) { + final String grp1 = nullMatcher.group(1); + final String str = arguments.containsKey(grp1) ? arguments.get(grp1).get() : null; + return str != null ? + quoteReplacement(str) : + quoteReplacement(arguments.getOrDefault(nullMatcher.group(2), () -> nullMatcher.group(2)).get()); + } + return quoteReplacement(arguments.getOrDefault(matchResult.group(1), () -> matchResult.group(0)).get()); + }); + } + + private final JanitorBot bot; + private final Map> defaultSubstitutions = new HashMap<>(); + + public SubstitutionMap(JanitorBot bot) { + this.bot = bot; + defaultSubstitutions.put("time.now", () -> OffsetDateTime.now(ZoneOffset.UTC).format(DATE_TIME_FORMAT)); + defaultSubstitutions.put("moderation.color", () -> "0xF1BD25"); + defaultSubstitutions.put("moderation.icon_url", + () -> "https://cdn.discordapp.com/attachments/738478941760782526/760463743330549760/gavel.png"); + defaultSubstitutions.put("general.error.color", () -> "0xF73132"); + } + + public JanitorBot getBot() { + return bot; + } + + public String substitute(String text) { + return SubstitutionMap.substitute(text, defaultSubstitutions); + } + + public String with(String text, Map> substitutions) { + return SubstitutionMap.substitute(text, createDefaultedMap(substitutions)); + } + + public CustomSubstitutions with(Map> customSubstitutions) { + return new CustomSubstitutions(createDefaultedMap(customSubstitutions)); + } + + public Map> createDefaultedMap(Map> custom) { + return DefaultedMap.defaultedMap(custom, TransformerUtils.mapTransformer(defaultSubstitutions)); + } + +} diff --git a/src/main/java/sciwhiz12/janitor/storage/GuildStorage.java b/src/main/java/sciwhiz12/janitor/storage/GuildStorage.java index c28cf91..8ac6fca 100644 --- a/src/main/java/sciwhiz12/janitor/storage/GuildStorage.java +++ b/src/main/java/sciwhiz12/janitor/storage/GuildStorage.java @@ -1,8 +1,6 @@ package sciwhiz12.janitor.storage; import com.google.common.base.Preconditions; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import net.dv8tion.jda.api.entities.Guild; import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.Logging; @@ -22,8 +20,6 @@ import static java.nio.file.StandardOpenOption.*; * A storage system for guild-specific data. */ public class GuildStorage { - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); - private final JanitorBot bot; private final Path mainFolder; private final Map>> guildStorages = new HashMap<>(); @@ -45,7 +41,7 @@ public class GuildStorage { public S getOrCreate(long guildID, StorageKey key, Supplier defaultSupplier) { final Map> storageMappy = guildStorages.computeIfAbsent(guildID, id -> new HashMap<>()); return key.getType().cast(storageMappy.computeIfAbsent(key.getStorageID(), - k -> new InnerStorage<>(key, load(guildID, key.getStorageID(), defaultSupplier.get()))).getStorage()); + k -> new InnerStorage<>(key, load(guildID, key.getStorageID(), defaultSupplier.get()))).getStorage()); } private Path getFile(long guildID, String key) { @@ -85,7 +81,7 @@ public class GuildStorage { if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent()); if (Files.notExists(file)) Files.createFile(file); try (Writer writer = Files - .newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) { + .newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) { inner.getStorage().write(writer); anySaved = true; } diff --git a/src/main/java/sciwhiz12/janitor/storage/IStorage.java b/src/main/java/sciwhiz12/janitor/storage/IStorage.java index 6ee78c8..655a775 100644 --- a/src/main/java/sciwhiz12/janitor/storage/IStorage.java +++ b/src/main/java/sciwhiz12/janitor/storage/IStorage.java @@ -1,5 +1,6 @@ package sciwhiz12.janitor.storage; +import java.io.IOException; import java.io.Reader; import java.io.Writer; @@ -7,7 +8,7 @@ public interface IStorage { boolean dirty(); - void write(Writer output); + void write(Writer output) throws IOException; - void read(Reader input); + void read(Reader input) throws IOException; } diff --git a/src/main/java/sciwhiz12/janitor/storage/JsonStorage.java b/src/main/java/sciwhiz12/janitor/storage/JsonStorage.java index f0a8013..5763234 100644 --- a/src/main/java/sciwhiz12/janitor/storage/JsonStorage.java +++ b/src/main/java/sciwhiz12/janitor/storage/JsonStorage.java @@ -1,30 +1,35 @@ package sciwhiz12.janitor.storage; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import java.io.IOException; import java.io.Reader; import java.io.Writer; public abstract class JsonStorage extends AbstractStorage { - public static final Gson GSON = new GsonBuilder() - .serializeNulls() - .setPrettyPrinting() - .create(); + protected final ObjectMapper jsonMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); - public abstract JsonElement save(); + protected JsonStorage() { + initialize(jsonMapper); + } - public abstract void load(JsonElement object); + protected void initialize(ObjectMapper mapper) {} + + public abstract JsonNode save(ObjectMapper mapper); + + public abstract void load(JsonNode object, ObjectMapper mapper) throws IOException; @Override - public void write(Writer input) { - GSON.toJson(save(), input); + public void write(Writer input) throws IOException { + jsonMapper.writeTree(jsonMapper.createGenerator(input), save(jsonMapper)); } @Override - public void read(Reader input) { - load(JsonParser.parseReader(input)); + public void read(Reader input) throws IOException { + load(jsonMapper.readTree(input), jsonMapper); } } diff --git a/src/main/java/sciwhiz12/janitor/storage/StorageKey.java b/src/main/java/sciwhiz12/janitor/storage/StorageKey.java index 5e5cddb..950bda4 100644 --- a/src/main/java/sciwhiz12/janitor/storage/StorageKey.java +++ b/src/main/java/sciwhiz12/janitor/storage/StorageKey.java @@ -57,7 +57,7 @@ public class StorageKey { if (o == null || getClass() != o.getClass()) return false; StorageKey that = (StorageKey) o; return storageID.equals(that.storageID) && - type.equals(that.type); + type.equals(that.type); } @Override diff --git a/src/main/java/sciwhiz12/janitor/utils/StringReaderUtil.java b/src/main/java/sciwhiz12/janitor/utils/StringReaderUtil.java index a20b0cc..9d2a5c7 100644 --- a/src/main/java/sciwhiz12/janitor/utils/StringReaderUtil.java +++ b/src/main/java/sciwhiz12/janitor/utils/StringReaderUtil.java @@ -37,8 +37,7 @@ public class StringReaderUtil { } private static final char SYNTAX_ESCAPE = '\\'; - private static final char SYNTAX_DOUBLE_QUOTE = '"'; - private static final char SYNTAX_SINGLE_QUOTE = '\''; + public static String readStringUntil(StringReader reader, char terminator) throws CommandSyntaxException { final StringBuilder result = new StringBuilder(); boolean escaped = false; @@ -50,7 +49,8 @@ public class StringReaderUtil { escaped = false; } else { reader.setCursor(reader.getCursor() - 1); - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidEscape().createWithContext(reader, String.valueOf(c)); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidEscape() + .createWithContext(reader, String.valueOf(c)); } } else if (c == SYNTAX_ESCAPE) { escaped = true; diff --git a/src/main/java/sciwhiz12/janitor/utils/Util.java b/src/main/java/sciwhiz12/janitor/utils/Util.java index 57ec9aa..fe22818 100644 --- a/src/main/java/sciwhiz12/janitor/utils/Util.java +++ b/src/main/java/sciwhiz12/janitor/utils/Util.java @@ -55,8 +55,8 @@ public class Util { return user.getName().concat("#").concat(user.getDiscriminator()); } - public static BiConsumer handle(final Consumer success, - final Consumer exceptionally) { + public static BiConsumer handle(final Consumer success, + final Consumer exceptionally) { return (suc, ex) -> { if (ex == null) { success.accept(suc); diff --git a/src/main/resources/english.json b/src/main/resources/english.json index 4de540a..1803669 100644 --- a/src/main/resources/english.json +++ b/src/main/resources/english.json @@ -1,100 +1,100 @@ { - "general.guild_only_command.title": "Guild only command!", - "general.guild_only_command.desc": "This command can only be run in a guild channel.", - "general.insufficient_permissions.title": "I have insufficient permissions!", - "general.insufficient_permissions.desc": "I do not have sufficient permissions to carry out this action!\nPlease contact your server admins if you believe this is in error.", - "general.insufficient_permissions.field.permissions": "Required permissions", - "general.ambiguous_member.title": "Ambiguous member argument!", - "general.ambiguous_member.desc": "The name you have specified is too ambiguous (leads to more than 1 member)!\nPlease narrow down the specified name until it can uniquely identify a member of this guild.", - "general.cannot_interact.title": "Member is higher than me!", - "general.cannot_interact.desc": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.", - "general.cannot_interact.field.target": "Target", - "general.cannot_action_self.title": "Cannot act against myself!", - "general.cannot_action_self.desc": "Cannot perform action against myself, as that would be counter-intuitive.", - "general.cannot_action_performer.title": "Performer cannot act against self!", - "general.cannot_action_performer.desc": "You cannot perform this action against yourself.", - "general.cannot_action_performer.field.performer": "Performer/Target", + "general.error.guild_only_command.title": "Guild only command!", + "general.error.guild_only_command.description": "This command can only be run in a guild channel.", + "general.error.ambiguous_member.title": "Ambiguous member argument!", + "general.error.ambiguous_member.description": "The name you have specified is too ambiguous (leads to more than 1 member)!\nPlease narrow down the specified name until it can uniquely identify a member of this guild.", + "general.error.insufficient_permissions.title": "I have insufficient permissions!", + "general.error.insufficient_permissions.description": "I do not have sufficient permissions to carry out this action!\nPlease contact your server admins if you believe this is in error.", + "general.error.insufficient_permissions.field.permissions": "Required permissions", + "general.error.cannot_interact.title": "Member is higher than me!", + "general.error.cannot_interact.description": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.", + "general.error.cannot_interact.field.target": "Target", + "general.error.cannot_action_self.title": "Cannot act against myself!", + "general.error.cannot_action_self.description": "Cannot perform action against myself, as that would be counter-intuitive.", + "general.error.cannot_action_performer.title": "Performer cannot act against self!", + "general.error.cannot_action_performer.description": "You cannot perform this action against yourself.", + "general.error.cannot_action_performer.field.performer": "Performer/Target", "moderation.insufficient_permissions.title": "Insufficient permissions.", - "moderation.insufficient_permissions.desc": "The performer of this command has insufficient permissions to use this command.", + "moderation.insufficient_permissions.description": "The performer of this command has insufficient permissions to use this command.", "moderation.insufficient_permissions.field.performer": "Performer", "moderation.insufficient_permissions.field.permissions": "Required permissions", "moderation.cannot_interact.title": "Cannot moderate Target.", - "moderation.cannot_interact.desc": "The performer of this command cannot moderate the target user, likely due to being lower in the role hierarchy.", + "moderation.cannot_interact.description": "The performer of this command cannot moderate the target user, likely due to being lower in the role hierarchy.", "moderation.cannot_interact.field.performer": "Performer", "moderation.cannot_interact.field.target": "Target", "moderation.kick.info.author": "Kicked user from server.", "moderation.kick.info.field.performer": "Performer", "moderation.kick.info.field.target": "Target", - "moderation.kick.info.field.reason": "Reason", - "moderation.kick.info.field.sent_private_message": "Sent DM", + "moderation.kick.info.field.private_message": "Sent DM", + "moderation.kick.info.field.reason.name": "Reason", + "moderation.kick.info.field.reason.value": "${nullcheck;reason;_No reason specified._}", "moderation.kick.dm.title": "You were kicked from this server.", "moderation.kick.dm.field.performer": "Moderator", - "moderation.kick.dm.field.reason": "Reason", + "moderation.kick.dm.field.reason.name": "Reason", + "moderation.kick.dm.field.reason.value": "${nullcheck;reason;_No reason specified._}", "moderation.ban.info.author": "Banned user from server.", "moderation.ban.info.field.performer": "Performer", "moderation.ban.info.field.target": "Target", - "moderation.ban.info.field.reason": "Reason", - "moderation.ban.info.field.sent_private_message": "Sent DM", - "moderation.ban.info.field.delete_duration": "Message Deletion", - "moderation.ban.info.field.delete_duration.value": "%s day(s)", + "moderation.ban.info.field.private_message": "Sent DM", + "moderation.ban.info.field.delete_duration.name": "Message Deletion", + "moderation.ban.info.field.delete_duration.value": "${delete_duration} day(s)", + "moderation.ban.info.field.reason.name": "Reason", + "moderation.ban.info.field.reason.value": "${nullcheck;reason;_No reason specified._}", "moderation.ban.dm.title": "You were banned from this server.", "moderation.ban.dm.field.performer": "Moderator", - "moderation.ban.dm.field.reason": "Reason", + "moderation.ban.dm.field.reason.name": "Reason", + "moderation.ban.dm.field.reason.value": "${nullcheck;reason;_No reason specified._}", "moderation.unban.info.author": "Unbanned user from server.", "moderation.unban.info.field.performer": "Performer", "moderation.unban.info.field.target": "Target", "moderation.warn.info.author": "Warned user.", "moderation.warn.info.field.performer": "Performer", "moderation.warn.info.field.target": "Target", - "moderation.warn.info.field.sent_private_message": "Sent DM", "moderation.warn.info.field.case_id": "Case ID", + "moderation.warn.info.field.reason.name": "Reason", + "moderation.warn.info.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}", + "moderation.warn.info.field.private_message": "Sent DM", "moderation.warn.info.field.date_time": "Date & Time", - "moderation.warn.info.field.reason": "Reason", "moderation.warn.dm.title": "You were warned by a moderator.", "moderation.warn.dm.field.performer": "Moderator", "moderation.warn.dm.field.date_time": "Date & Time", - "moderation.warn.dm.field.reason": "Reason", - "moderation.warnlist.author": "Listing of Warnings", - "moderation.warnlist.empty": "**_No warnings logged matching your query._**", - "moderation.warnlist.entry": "**Case #%1$s**: Warned %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Reason:_ %5$s", - "moderation.warnlist.entry.no_reason": "_no reason specified_", - "moderation.unwarn.author": "Removed warning from user.", - "moderation.unwarn.field.performer": "Performer", - "moderation.unwarn.field.original_target": "Original Target", - "moderation.unwarn.field.original_performer": "Original Performer", - "moderation.unwarn.field.case_id": "Case ID", - "moderation.unwarn.field.date_time": "Date & Time", - "moderation.unwarn.field.reason": "Reason", + "moderation.warn.dm.field.reason.name": "Reason", + "moderation.warn.dm.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}", + "moderation.unwarn.info.author": "Removed warning from user.", + "moderation.unwarn.info.field.performer": "Performer", + "moderation.unwarn.info.field.original_target": "Original Target", + "moderation.unwarn.info.field.original_performer": "Original Performer", + "moderation.unwarn.info.field.case_id": "Case ID", + "moderation.unwarn.info.field.date_time": "Date & Time", + "moderation.unwarn.info.field.reason.name": "Reason", + "moderation.unwarn.info.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}", "moderation.unwarn.no_case_found.title": "No warning found.", - "moderation.unwarn.no_case_found.desc": "No warning with that case ID was found.", + "moderation.unwarn.no_case_found.description": "No warning with that case ID was found.", "moderation.unwarn.no_case_found.field.performer": "Performer", "moderation.unwarn.no_case_found.field.case_id": "Case ID", "moderation.unwarn.cannot_unwarn_self.title": "Cannot remove warning from self.", - "moderation.unwarn.cannot_unwarn_self.desc": "Performer cannot remove a warning from themselves.", + "moderation.unwarn.cannot_unwarn_self.description": "Performer cannot remove a warning from themselves.", "moderation.unwarn.cannot_unwarn_self.field.performer": "Performer/Original Target", "moderation.unwarn.cannot_unwarn_self.field.original_performer": "Original Performer", "moderation.unwarn.cannot_unwarn_self.field.case_id": "Case ID", "moderation.warn.cannot_warn_mods.title": "Cannot warn moderators.", - "moderation.warn.cannot_warn_mods.desc": "Moderators cannot issue warnings to other moderators.", + "moderation.warn.cannot_warn_mods.description": "Moderators cannot issue warnings to other moderators.", "moderation.warn.cannot_warn_mods.field.performer": "Performer", "moderation.warn.cannot_warn_mods.field.target": "Target", "moderation.warn.cannot_remove_higher_mod.title": "Cannot remove warning issued by higher-ranked moderator.", - "moderation.warn.cannot_remove_higher_mod.desc": "The performer cannot remove this warning, as this was issued by a higher-ranking moderator.", + "moderation.warn.cannot_remove_higher_mod.description": "The performer cannot remove this warning, as this was issued by a higher-ranking moderator.", "moderation.warn.cannot_remove_higher_mod.field.performer": "Performer", "moderation.warn.cannot_remove_higher_mod.field.original_performer": "Original Performer", "moderation.warn.cannot_remove_higher_mod.field.case_id": "Case ID", "moderation.note.max_amount_of_notes.title": "Max notes reached.", - "moderation.note.max_amount_of_notes.desc": "The performer has reached the maximum amount of notes for the target user.", + "moderation.note.max_amount_of_notes.description": "The performer has reached the maximum amount of notes for the target user.", "moderation.note.max_amount_of_notes.field.performer": "Performer", "moderation.note.max_amount_of_notes.field.target": "Target", "moderation.note.max_amount_of_notes.field.amount": "(Max.) Amount", "moderation.note.no_note_found.title": "No note found.", - "moderation.note.no_note_found.desc": "No note with that note ID was found.", + "moderation.note.no_note_found.description": "No note with that note ID was found.", "moderation.note.no_note_found.field.performer": "Performer", "moderation.note.no_note_found.field.note_id": "Note ID", - "moderation.note.list.author": "Listing of Notes", - "moderation.note.list.empty": "**_No recorded notes matching your query._**", - "moderation.note.list.entry": "**#%1$s**: for %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Text:_ %5$s", "moderation.note.add.author": "Recorded note for user.", "moderation.note.add.field.performer": "Performer", "moderation.note.add.field.target": "Target", @@ -103,9 +103,15 @@ "moderation.note.add.field.contents": "Text", "moderation.note.remove.author": "Removed note.", "moderation.note.remove.field.performer": "Performer", - "moderation.note.remove.field.original_target": "Original Target", "moderation.note.remove.field.original_performer": "Original Performer", + "moderation.note.remove.field.original_target": "Original Target", "moderation.note.remove.field.note_id": "Note ID", "moderation.note.remove.field.date_time": "Date & Time", - "moderation.note.remove.field.contents": "Text" + "moderation.note.remove.field.contents": "Text", + "moderation.warnlist.author": "Listing of Warnings", + "moderation.warnlist.empty": "**_No warnings logged matching your query._**", + "moderation.warnlist.entry": "**Case #${warning_entry.case_id}**: Warned ${warning_entry.warned.mention} by ${warning_entry.performer.mention} \n - _Date & Time:_ ${warning_entry.date_time} \n - _Reason:_ ${nullcheck;warning_entry.reason;_No reason specified._}", + "moderation.note.list.author": "Listing of Notes (Page ${page.current}/${page.max})", + "moderation.note.list.empty": "**_No recorded notes matching your query._**", + "moderation.note.list.entry": "**#${note_entry.note_id}**: for ${note_entry.target.mention} by ${note_entry.performer.mention} \n - _Date & Time:_ ${note_entry.date_time} \n - _Text:_ ${note_entry.contents}" } \ No newline at end of file diff --git a/src/main/resources/messages/general/error/ambiguous_member.json b/src/main/resources/messages/general/error/ambiguous_member.json new file mode 100644 index 0000000..f078f45 --- /dev/null +++ b/src/main/resources/messages/general/error/ambiguous_member.json @@ -0,0 +1,5 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "" +} \ No newline at end of file diff --git a/src/main/resources/messages/general/error/cannot_action_performer.json b/src/main/resources/messages/general/error/cannot_action_performer.json new file mode 100644 index 0000000..8913518 --- /dev/null +++ b/src/main/resources/messages/general/error/cannot_action_performer.json @@ -0,0 +1,12 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/general/error/cannot_action_self.json b/src/main/resources/messages/general/error/cannot_action_self.json new file mode 100644 index 0000000..a43ec63 --- /dev/null +++ b/src/main/resources/messages/general/error/cannot_action_self.json @@ -0,0 +1,5 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "" +} \ No newline at end of file diff --git a/src/main/resources/messages/general/error/cannot_interact.json b/src/main/resources/messages/general/error/cannot_interact.json new file mode 100644 index 0000000..a4eea52 --- /dev/null +++ b/src/main/resources/messages/general/error/cannot_interact.json @@ -0,0 +1,12 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${target.mention}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/general/error/guild_only_command.json b/src/main/resources/messages/general/error/guild_only_command.json new file mode 100644 index 0000000..b46d90c --- /dev/null +++ b/src/main/resources/messages/general/error/guild_only_command.json @@ -0,0 +1,5 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "" +} \ No newline at end of file diff --git a/src/main/resources/messages/general/error/insufficient_permissions.json b/src/main/resources/messages/general/error/insufficient_permissions.json new file mode 100644 index 0000000..1d3f29c --- /dev/null +++ b/src/main/resources/messages/general/error/insufficient_permissions.json @@ -0,0 +1,12 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${required_permissions}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/messages.json b/src/main/resources/messages/messages.json new file mode 100644 index 0000000..fe64a1b --- /dev/null +++ b/src/main/resources/messages/messages.json @@ -0,0 +1,28 @@ +[ + "general/error/ambiguous_member", + "general/error/guild_only_command", + "general/error/insufficient_permissions", + "general/error/cannot_interact", + "general/error/cannot_action_self", + "general/error/cannot_action_performer", + "moderation/error/cannot_interact", + "moderation/error/insufficient_permissions", + "moderation/kick/info", + "moderation/kick/dm", + "moderation/ban/info", + "moderation/ban/dm", + "moderation/unban/info", + "moderation/warn/info", + "moderation/warn/dm", + "moderation/warn/list", + "moderation/unwarn/info", + "moderation/error/unwarn/no_case_found", + "moderation/error/unwarn/cannot_unwarn_self", + "moderation/error/unwarn/cannot_remove_higher_mod", + "moderation/error/warn/cannot_warn_mods", + "moderation/error/note/max_amount_of_notes", + "moderation/error/note/no_note_found", + "moderation/note/add", + "moderation/note/remove", + "moderation/note/list" +] \ No newline at end of file diff --git a/src/main/resources/messages/moderation/ban/dm.json b/src/main/resources/messages/moderation/ban/dm.json new file mode 100644 index 0000000..3798ed0 --- /dev/null +++ b/src/main/resources/messages/moderation/ban/dm.json @@ -0,0 +1,20 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "${performer.guild.name}", + "icon_url": "${performer.guild.icon_url}" + }, + "title": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/ban/info.json b/src/main/resources/messages/moderation/ban/info.json new file mode 100644 index 0000000..306ee27 --- /dev/null +++ b/src/main/resources/messages/moderation/ban/info.json @@ -0,0 +1,34 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + }, + { + "name": "", + "value": "${private_message}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": true + }, + { + "name": "", + "value": "", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/cannot_interact.json b/src/main/resources/messages/moderation/error/cannot_interact.json new file mode 100644 index 0000000..63cb40a --- /dev/null +++ b/src/main/resources/messages/moderation/error/cannot_interact.json @@ -0,0 +1,17 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/insufficient_permissions.json b/src/main/resources/messages/moderation/error/insufficient_permissions.json new file mode 100644 index 0000000..a2479f2 --- /dev/null +++ b/src/main/resources/messages/moderation/error/insufficient_permissions.json @@ -0,0 +1,17 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${required_permissions}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/note/max_amount_of_notes.json b/src/main/resources/messages/moderation/error/note/max_amount_of_notes.json new file mode 100644 index 0000000..a451a0d --- /dev/null +++ b/src/main/resources/messages/moderation/error/note/max_amount_of_notes.json @@ -0,0 +1,22 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + }, + { + "name": "", + "value": "${notes_amount}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/note/no_note_found.json b/src/main/resources/messages/moderation/error/note/no_note_found.json new file mode 100644 index 0000000..70b2bf3 --- /dev/null +++ b/src/main/resources/messages/moderation/error/note/no_note_found.json @@ -0,0 +1,17 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_id}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/unwarn/cannot_remove_higher_mod.json b/src/main/resources/messages/moderation/error/unwarn/cannot_remove_higher_mod.json new file mode 100644 index 0000000..fd3982d --- /dev/null +++ b/src/main/resources/messages/moderation/error/unwarn/cannot_remove_higher_mod.json @@ -0,0 +1,22 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.case_id}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/unwarn/cannot_unwarn_self.json b/src/main/resources/messages/moderation/error/unwarn/cannot_unwarn_self.json new file mode 100644 index 0000000..69f09a3 --- /dev/null +++ b/src/main/resources/messages/moderation/error/unwarn/cannot_unwarn_self.json @@ -0,0 +1,22 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.case_id}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/unwarn/no_case_found.json b/src/main/resources/messages/moderation/error/unwarn/no_case_found.json new file mode 100644 index 0000000..2914238 --- /dev/null +++ b/src/main/resources/messages/moderation/error/unwarn/no_case_found.json @@ -0,0 +1,17 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${case_id}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/error/warn/cannot_warn_mods.json b/src/main/resources/messages/moderation/error/warn/cannot_warn_mods.json new file mode 100644 index 0000000..4b2f418 --- /dev/null +++ b/src/main/resources/messages/moderation/error/warn/cannot_warn_mods.json @@ -0,0 +1,17 @@ +{ + "color": "${general.error.color}", + "title": "", + "description": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/kick/dm.json b/src/main/resources/messages/moderation/kick/dm.json new file mode 100644 index 0000000..89a7ca0 --- /dev/null +++ b/src/main/resources/messages/moderation/kick/dm.json @@ -0,0 +1,20 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "${performer.guild.name}", + "icon_url": "${performer.guild.icon_url}" + }, + "title": "", + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/kick/info.json b/src/main/resources/messages/moderation/kick/info.json new file mode 100644 index 0000000..f3288b8 --- /dev/null +++ b/src/main/resources/messages/moderation/kick/info.json @@ -0,0 +1,29 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + }, + { + "name": "", + "value": "${private_message}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/note/add.json b/src/main/resources/messages/moderation/note/add.json new file mode 100644 index 0000000..71fc360 --- /dev/null +++ b/src/main/resources/messages/moderation/note/add.json @@ -0,0 +1,34 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${note_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.target.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.note_id}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.date_time}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.contents}", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/note/list.json b/src/main/resources/messages/moderation/note/list.json new file mode 100644 index 0000000..db23518 --- /dev/null +++ b/src/main/resources/messages/moderation/note/list.json @@ -0,0 +1,13 @@ +{ + "type": "listing", + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "entry": { + "type": "description", + "text": "" + }, + "empty": "" +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/note/remove.json b/src/main/resources/messages/moderation/note/remove.json new file mode 100644 index 0000000..69b176b --- /dev/null +++ b/src/main/resources/messages/moderation/note/remove.json @@ -0,0 +1,39 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.target.mention}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.note_id}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.date_time}", + "inline": true + }, + { + "name": "", + "value": "${note_entry.contents}", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/unban/info.json b/src/main/resources/messages/moderation/unban/info.json new file mode 100644 index 0000000..6efe8a3 --- /dev/null +++ b/src/main/resources/messages/moderation/unban/info.json @@ -0,0 +1,19 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${target.mention}", + "inline": true + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/unwarn/info.json b/src/main/resources/messages/moderation/unwarn/info.json new file mode 100644 index 0000000..6d2b347 --- /dev/null +++ b/src/main/resources/messages/moderation/unwarn/info.json @@ -0,0 +1,39 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.target.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.case_id}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.date_time}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/warn/dm.json b/src/main/resources/messages/moderation/warn/dm.json new file mode 100644 index 0000000..1139d25 --- /dev/null +++ b/src/main/resources/messages/moderation/warn/dm.json @@ -0,0 +1,25 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "${performer.guild.name}", + "icon_url": "${performer.guild.icon_url}" + }, + "title": "", + "fields": [ + { + "name": "", + "value": "${warning_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.date_time}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/warn/info.json b/src/main/resources/messages/moderation/warn/info.json new file mode 100644 index 0000000..ed55bb7 --- /dev/null +++ b/src/main/resources/messages/moderation/warn/info.json @@ -0,0 +1,39 @@ +{ + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "fields": [ + { + "name": "", + "value": "${warning_entry.performer.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.target.mention}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.case_id}", + "inline": true + }, + { + "name": "", + "value": "${private_message}", + "inline": true + }, + { + "name": "", + "value": "${warning_entry.date_time}", + "inline": true + }, + { + "name": "", + "value": "", + "inline": false + } + ] +} \ No newline at end of file diff --git a/src/main/resources/messages/moderation/warn/list.json b/src/main/resources/messages/moderation/warn/list.json new file mode 100644 index 0000000..b915a59 --- /dev/null +++ b/src/main/resources/messages/moderation/warn/list.json @@ -0,0 +1,13 @@ +{ + "type": "listing", + "color": "${moderation.color}", + "author": { + "name": "", + "icon_url": "${moderation.icon_url}" + }, + "entry": { + "type": "description", + "text": "" + }, + "empty": "" +} \ No newline at end of file