diff --git a/src/main/java/sciwhiz12/janitor/BotConsole.java b/src/main/java/sciwhiz12/janitor/BotConsole.java index 67d0ede..4f2526a 100644 --- a/src/main/java/sciwhiz12/janitor/BotConsole.java +++ b/src/main/java/sciwhiz12/janitor/BotConsole.java @@ -34,14 +34,25 @@ public class BotConsole { public void parseCommand(String input) { String[] parts = input.split(" "); + outer: switch (parts[0]) { case "shutdown": { running = false; bot.shutdown(); break; } + case "reload": { + if (parts.length >= 2) + switch (parts[1]) { + case "translations": { + CONSOLE.info("Reloading translations"); + bot.getTranslations().loadTranslations(); + break outer; + } + } + } default: - CONSOLE.warn("Unknown command: " + input); + CONSOLE.warn("Unknown command: {}", input); } } @@ -60,7 +71,8 @@ public class BotConsole { while (!scanner.hasNextLine()) { try { Thread.sleep(150); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { CONSOLE.warn("Console thread is interrupted"); continue outer; } @@ -72,7 +84,8 @@ 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/GuildStorage.java b/src/main/java/sciwhiz12/janitor/GuildStorage.java index a69e3f7..9c98f7b 100644 --- a/src/main/java/sciwhiz12/janitor/GuildStorage.java +++ b/src/main/java/sciwhiz12/janitor/GuildStorage.java @@ -62,7 +62,12 @@ public class GuildStorage { } public void save() { - Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder); + save(false); + } + + public void save(boolean isAutosave) { + if (!isAutosave) + Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder); boolean anySaved = false; for (Guild guild : guildStorage.keySet()) { final Map storageMap = guildStorage.get(guild); @@ -111,8 +116,8 @@ public class GuildStorage { @Override public void run() { while (running) { - storage.save(); - try { Thread.sleep(10000); } + storage.save(true); + try { Thread.sleep(storage.getBot().getConfig().AUTOSAVE_INTERVAL.get() * 1000); } catch (InterruptedException ignored) {} } } diff --git a/src/main/java/sciwhiz12/janitor/commands/BaseCommand.java b/src/main/java/sciwhiz12/janitor/commands/BaseCommand.java index 4f115d4..334e362 100644 --- a/src/main/java/sciwhiz12/janitor/commands/BaseCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/BaseCommand.java @@ -3,6 +3,7 @@ package sciwhiz12.janitor.commands; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import sciwhiz12.janitor.JanitorBot; +import sciwhiz12.janitor.config.BotConfig; import sciwhiz12.janitor.msg.Messages; public abstract class BaseCommand { @@ -24,5 +25,9 @@ public abstract class BaseCommand { return getBot().getMessages(); } + protected BotConfig config() { + return getBot().getConfig(); + } + public abstract LiteralArgumentBuilder getNode(); } diff --git a/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java b/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java index 46ef45b..71f5bde 100644 --- a/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java +++ b/src/main/java/sciwhiz12/janitor/commands/CommandRegistry.java @@ -48,9 +48,7 @@ public class CommandRegistry implements EventListener { addCommand(new WarnCommand(this)); addCommand(new WarnListCommand(this)); addCommand(new UnwarnCommand(this)); - if (bot.getConfig().getOwnerID().isPresent()) { - addCommand(new ShutdownCommand(this, bot.getConfig().getOwnerID().get())); - } + addCommand(new ShutdownCommand(this)); } public CommandDispatcher getDispatcher() { diff --git a/src/main/java/sciwhiz12/janitor/commands/bot/ShutdownCommand.java b/src/main/java/sciwhiz12/janitor/commands/bot/ShutdownCommand.java index 89c1a82..ca35e03 100644 --- a/src/main/java/sciwhiz12/janitor/commands/bot/ShutdownCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/bot/ShutdownCommand.java @@ -11,17 +11,16 @@ import static sciwhiz12.janitor.Logging.JANITOR; import static sciwhiz12.janitor.commands.util.CommandHelper.literal; public class ShutdownCommand extends BaseCommand { - private final long ownerID; - - public ShutdownCommand(CommandRegistry registry, long ownerID) { + public ShutdownCommand(CommandRegistry registry) { super(registry); - this.ownerID = ownerID; } @Override public LiteralArgumentBuilder getNode() { return literal("shutdown") - .requires(ctx -> ctx.getAuthor().getIdLong() == ownerID) + .requires(ctx -> getBot().getConfig().getOwnerID().map( + id -> id == ctx.getAuthor().getIdLong()).orElse(false) + ) .executes(this::run); } @@ -33,7 +32,8 @@ public class ShutdownCommand extends BaseCommand { .submit() .whenComplete(Util.handle( success -> JANITOR.debug("Sent shutdown message to channel {}", Util.toString(ctx.getSource().getAuthor())), - err -> JANITOR.error("Error while sending ping message to bot owner {}", Util.toString(ctx.getSource().getAuthor())) + err -> JANITOR + .error("Error while sending ping message to bot owner {}", Util.toString(ctx.getSource().getAuthor())) ) ) .join(); diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java index 01fed67..a5129c5 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/UnwarnCommand.java @@ -31,6 +31,7 @@ public class UnwarnCommand extends BaseCommand { @Override public LiteralArgumentBuilder getNode() { return literal("unwarn") + .requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get()) .then(argument("caseId", IntegerArgumentType.integer(1)) .executes(this::run) ); @@ -59,10 +60,16 @@ public class UnwarnCommand extends BaseCommand { final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild); @Nullable final WarningEntry entry = storage.getWarning(caseID); + Member temp; if (entry == null) messages().MODERATION.noWarnWithID(channel, performer, caseID).queue(); - else if (entry.getWarned().getIdLong() == performer.getIdLong()) + else if (entry.getWarned().getIdLong() == performer.getIdLong() + && !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) messages().MODERATION.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.cannotRemoveHigherModerated(channel, performer, caseID, entry).queue(); else { storage.removeWarning(caseID); messages().MODERATION.unwarn(channel, performer, caseID, entry).queue(); diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java index 1b46de6..685e9bd 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnCommand.java @@ -36,6 +36,7 @@ public class WarnCommand extends BaseCommand { @Override public LiteralArgumentBuilder getNode() { return literal("warn") + .requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get()) .then(argument("member", member()) .then(argument("reason", greedyString()) .executes(ctx -> this.run(ctx, getString(ctx, "reason"))) @@ -70,6 +71,8 @@ public class WarnCommand extends BaseCommand { messages().MODERATION.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); else if (!performer.canInteract(target)) messages().MODERATION.cannotModerate(channel, performer, target).queue(); + else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) + messages().MODERATION.cannotWarnMods(channel, performer, target).queue(); else target.getUser().openPrivateChannel() .flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime)) diff --git a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java index 14fbbbc..85b7217 100644 --- a/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java +++ b/src/main/java/sciwhiz12/janitor/commands/moderation/WarnListCommand.java @@ -36,6 +36,7 @@ public class WarnListCommand extends BaseCommand { @Override public LiteralArgumentBuilder getNode() { return literal("warnlist") + .requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get()) .then(literal("target") .then(argument("target", member()) .then(literal("mod") diff --git a/src/main/java/sciwhiz12/janitor/config/BotConfig.java b/src/main/java/sciwhiz12/janitor/config/BotConfig.java index 91617ff..50be96e 100644 --- a/src/main/java/sciwhiz12/janitor/config/BotConfig.java +++ b/src/main/java/sciwhiz12/janitor/config/BotConfig.java @@ -18,10 +18,19 @@ public class BotConfig { private final CommentedConfigSpec.ConfigValue CLIENT_TOKEN; private final CommentedConfigSpec.LongValue OWNER_ID; + public final CommentedConfigSpec.ConfigValue STORAGE_PATH; + public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL; + public final CommentedConfigSpec.ConfigValue CUSTOM_TRANSLATION_FILE; + public final CommentedConfigSpec.ConfigValue COMMAND_PREFIX; + public final CommentedConfigSpec.BooleanValue WARNINGS_ENABLE; + public final CommentedConfigSpec.BooleanValue WARNINGS_RESPECT_MOD_ROLES; + public final CommentedConfigSpec.BooleanValue WARNINGS_PREVENT_WARNING_MODS; + public final CommentedConfigSpec.BooleanValue WARNINGS_REMOVE_SELF_WARNINGS; + private final BotOptions options; private final Path configPath; private final CommentedConfigSpec spec; @@ -32,24 +41,55 @@ public class BotConfig { final CommentedConfigSpec.Builder builder = new CommentedConfigSpec.Builder(); + builder.push("discord"); CLIENT_TOKEN = builder .comment("The client secret/token for the bot user", "This must be set, or the application will not start up.") - .define("discord.client_token", ""); + .define("client_token", ""); OWNER_ID = builder .comment("The id of the bot owner; used for sending status messages and for bot administration commands.", "If 0, then the bot has no owner set.") - .defineInRange("discord.owner_id", 0L, Long.MIN_VALUE, Long.MAX_VALUE); + .defineInRange("owner_id", 0L, Long.MIN_VALUE, Long.MAX_VALUE); + builder.pop(); + + builder.push("storage"); STORAGE_PATH = builder .comment("The folder where per-guild storage is kept.") - .define("storage.main_path", "guild_storage"); + .define("main_path", "guild_storage"); + AUTOSAVE_INTERVAL = builder + .comment("The interval between storage autosave checks, in seconds.") + .defineInRange("autosave_internal", 20, 1, Integer.MAX_VALUE); + builder.pop(); + CUSTOM_TRANSLATION_FILE = builder .comment("A file which contains custom translation keys to load for messages.", "If blank, no file shall be loaded.") .define("messages.custom_translations", ""); + COMMAND_PREFIX = builder .comment("The prefix for commands.") .define("commands.prefix", "!"); + builder.comment("Moderation settings").push("moderation"); + { + builder.comment("Settings for the warnings system").push("warnings"); + WARNINGS_ENABLE = builder + .comment("Whether to enable the warnings system. If disabled, the related commands are force-disabled.") + .define("enable", true); + WARNINGS_RESPECT_MOD_ROLES = builder + .comment( + "Whether to prevent lower-ranked moderators (in the role hierarchy) from removing warnings issued by " + + "higher-ranked moderators.") + .define("respect_mod_roles", false); + WARNINGS_PREVENT_WARNING_MODS = builder + .comment("Whether to prevent moderators from issuing warnings against other moderators.") + .define("warn_other_moderators", false); + WARNINGS_REMOVE_SELF_WARNINGS = builder + .comment("Whether to allow moderators to remove warnings from themselves.") + .define("remove_self_warnings", false); + builder.pop(); + } + builder.pop(); + spec = builder.build(); this.configPath = options.getConfigPath().orElse(DEFAULT_CONFIG_PATH); diff --git a/src/main/java/sciwhiz12/janitor/msg/Messages.java b/src/main/java/sciwhiz12/janitor/msg/Messages.java index 79487e0..86e7dd0 100644 --- a/src/main/java/sciwhiz12/janitor/msg/Messages.java +++ b/src/main/java/sciwhiz12/janitor/msg/Messages.java @@ -312,6 +312,33 @@ public class Messages { embed.addField(translate("moderation.unwarn.field.reason"), entry.getReason(), false); return channel.sendMessage(embed.build()); } + + public MessageAction cannotWarnMods(MessageChannel channel, Member performer, Member target) { + final EmbedBuilder embed = new EmbedBuilder() + .setTitle(translate("moderation.warn.cannot_warn_mods.title"), null) + .setColor(General.FAILURE_COLOR) + .setTimestamp(OffsetDateTime.now(Clock.systemUTC())) + .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); + return channel.sendMessage(embed.build()); + } + + public MessageAction cannotRemoveHigherModerated(MessageChannel channel, Member performer, int caseID, WarningEntry entry) { + final EmbedBuilder embed = new EmbedBuilder() + .setTitle(translate("moderation.unwarn.cannot_remove_higher_mod.title"), null) + .setColor(General.FAILURE_COLOR) + .setTimestamp(OffsetDateTime.now(Clock.systemUTC())) + .setDescription(translate("moderation.unwarn.cannot_remove_higher_mod.desc")) + .addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.performer"), + performer.getUser().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); + return channel.sendMessage(embed.build()); + } } } diff --git a/src/main/java/sciwhiz12/janitor/msg/Translations.java b/src/main/java/sciwhiz12/janitor/msg/Translations.java index a280e5a..5dda90d 100644 --- a/src/main/java/sciwhiz12/janitor/msg/Translations.java +++ b/src/main/java/sciwhiz12/janitor/msg/Translations.java @@ -32,7 +32,7 @@ public class Translations { loadTranslations(); } - void loadTranslations() { + public void loadTranslations() { if (translationsFile == null) { JANITOR.info(TRANSLATIONS, "No translation file given, using default english translations"); loadDefaultTranslations(); diff --git a/src/main/resources/english.json b/src/main/resources/english.json index cbe70ad..43ec741 100644 --- a/src/main/resources/english.json +++ b/src/main/resources/english.json @@ -72,5 +72,14 @@ "moderation.unwarn.cannot_unwarn_self.desc": "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.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.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.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" } \ No newline at end of file