1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-09-19 23:24:03 +00:00

Improve and add more config options, add reload command to console

This commit is contained in:
Arnold Alejo Nunag 2020-10-01 05:22:40 +08:00
parent cbf57fb585
commit 934fbeb2f4
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
12 changed files with 129 additions and 21 deletions

View File

@ -34,14 +34,25 @@ public class BotConsole {
public void parseCommand(String input) { public void parseCommand(String input) {
String[] parts = input.split(" "); String[] parts = input.split(" ");
outer:
switch (parts[0]) { switch (parts[0]) {
case "shutdown": { case "shutdown": {
running = false; running = false;
bot.shutdown(); bot.shutdown();
break; break;
} }
case "reload": {
if (parts.length >= 2)
switch (parts[1]) {
case "translations": {
CONSOLE.info("Reloading translations");
bot.getTranslations().loadTranslations();
break outer;
}
}
}
default: default:
CONSOLE.warn("Unknown command: " + input); CONSOLE.warn("Unknown command: {}", input);
} }
} }
@ -60,7 +71,8 @@ public class BotConsole {
while (!scanner.hasNextLine()) { while (!scanner.hasNextLine()) {
try { try {
Thread.sleep(150); Thread.sleep(150);
} catch (InterruptedException e) { }
catch (InterruptedException e) {
CONSOLE.warn("Console thread is interrupted"); CONSOLE.warn("Console thread is interrupted");
continue outer; continue outer;
} }
@ -72,7 +84,8 @@ public class BotConsole {
} }
CONSOLE.debug("Received command: {}", input); CONSOLE.debug("Received command: {}", input);
BotConsole.this.parseCommand(input); BotConsole.this.parseCommand(input);
} catch (Exception e) { }
catch (Exception e) {
CONSOLE.error("Error while running console thread", e); CONSOLE.error("Error while running console thread", e);
} }
} }

View File

@ -62,7 +62,12 @@ public class GuildStorage {
} }
public void save() { 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; boolean anySaved = false;
for (Guild guild : guildStorage.keySet()) { for (Guild guild : guildStorage.keySet()) {
final Map<String, IStorage> storageMap = guildStorage.get(guild); final Map<String, IStorage> storageMap = guildStorage.get(guild);
@ -111,8 +116,8 @@ public class GuildStorage {
@Override @Override
public void run() { public void run() {
while (running) { while (running) {
storage.save(); storage.save(true);
try { Thread.sleep(10000); } try { Thread.sleep(storage.getBot().getConfig().AUTOSAVE_INTERVAL.get() * 1000); }
catch (InterruptedException ignored) {} catch (InterruptedException ignored) {}
} }
} }

View File

@ -3,6 +3,7 @@ package sciwhiz12.janitor.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.msg.Messages; import sciwhiz12.janitor.msg.Messages;
public abstract class BaseCommand { public abstract class BaseCommand {
@ -24,5 +25,9 @@ public abstract class BaseCommand {
return getBot().getMessages(); return getBot().getMessages();
} }
protected BotConfig config() {
return getBot().getConfig();
}
public abstract LiteralArgumentBuilder<MessageReceivedEvent> getNode(); public abstract LiteralArgumentBuilder<MessageReceivedEvent> getNode();
} }

View File

@ -48,9 +48,7 @@ public class CommandRegistry implements EventListener {
addCommand(new WarnCommand(this)); addCommand(new WarnCommand(this));
addCommand(new WarnListCommand(this)); addCommand(new WarnListCommand(this));
addCommand(new UnwarnCommand(this)); addCommand(new UnwarnCommand(this));
if (bot.getConfig().getOwnerID().isPresent()) { addCommand(new ShutdownCommand(this));
addCommand(new ShutdownCommand(this, bot.getConfig().getOwnerID().get()));
}
} }
public CommandDispatcher<MessageReceivedEvent> getDispatcher() { public CommandDispatcher<MessageReceivedEvent> getDispatcher() {

View File

@ -11,17 +11,16 @@ import static sciwhiz12.janitor.Logging.JANITOR;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal; import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
public class ShutdownCommand extends BaseCommand { public class ShutdownCommand extends BaseCommand {
private final long ownerID; public ShutdownCommand(CommandRegistry registry) {
public ShutdownCommand(CommandRegistry registry, long ownerID) {
super(registry); super(registry);
this.ownerID = ownerID;
} }
@Override @Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() { public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("shutdown") 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); .executes(this::run);
} }
@ -33,7 +32,8 @@ public class ShutdownCommand extends BaseCommand {
.submit() .submit()
.whenComplete(Util.handle( .whenComplete(Util.handle(
success -> JANITOR.debug("Sent shutdown message to channel {}", Util.toString(ctx.getSource().getAuthor())), 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(); .join();

View File

@ -31,6 +31,7 @@ public class UnwarnCommand extends BaseCommand {
@Override @Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() { public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("unwarn") return literal("unwarn")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.then(argument("caseId", IntegerArgumentType.integer(1)) .then(argument("caseId", IntegerArgumentType.integer(1))
.executes(this::run) .executes(this::run)
); );
@ -59,10 +60,16 @@ public class UnwarnCommand extends BaseCommand {
final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild); final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild);
@Nullable @Nullable
final WarningEntry entry = storage.getWarning(caseID); final WarningEntry entry = storage.getWarning(caseID);
Member temp;
if (entry == null) if (entry == null)
messages().MODERATION.noWarnWithID(channel, performer, caseID).queue(); 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(); 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 { else {
storage.removeWarning(caseID); storage.removeWarning(caseID);
messages().MODERATION.unwarn(channel, performer, caseID, entry).queue(); messages().MODERATION.unwarn(channel, performer, caseID, entry).queue();

View File

@ -36,6 +36,7 @@ public class WarnCommand extends BaseCommand {
@Override @Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() { public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warn") return literal("warn")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.then(argument("member", member()) .then(argument("member", member())
.then(argument("reason", greedyString()) .then(argument("reason", greedyString())
.executes(ctx -> this.run(ctx, getString(ctx, "reason"))) .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(); messages().MODERATION.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else if (!performer.canInteract(target)) else if (!performer.canInteract(target))
messages().MODERATION.cannotModerate(channel, performer, target).queue(); 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 else
target.getUser().openPrivateChannel() target.getUser().openPrivateChannel()
.flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime)) .flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime))

View File

@ -36,6 +36,7 @@ public class WarnListCommand extends BaseCommand {
@Override @Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() { public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warnlist") return literal("warnlist")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.then(literal("target") .then(literal("target")
.then(argument("target", member()) .then(argument("target", member())
.then(literal("mod") .then(literal("mod")

View File

@ -18,10 +18,19 @@ public class BotConfig {
private final CommentedConfigSpec.ConfigValue<String> CLIENT_TOKEN; private final CommentedConfigSpec.ConfigValue<String> CLIENT_TOKEN;
private final CommentedConfigSpec.LongValue OWNER_ID; private final CommentedConfigSpec.LongValue OWNER_ID;
public final CommentedConfigSpec.ConfigValue<String> STORAGE_PATH; public final CommentedConfigSpec.ConfigValue<String> STORAGE_PATH;
public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL;
public final CommentedConfigSpec.ConfigValue<String> CUSTOM_TRANSLATION_FILE; public final CommentedConfigSpec.ConfigValue<String> CUSTOM_TRANSLATION_FILE;
public final CommentedConfigSpec.ConfigValue<String> COMMAND_PREFIX; public final CommentedConfigSpec.ConfigValue<String> 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 BotOptions options;
private final Path configPath; private final Path configPath;
private final CommentedConfigSpec spec; private final CommentedConfigSpec spec;
@ -32,24 +41,55 @@ public class BotConfig {
final CommentedConfigSpec.Builder builder = new CommentedConfigSpec.Builder(); final CommentedConfigSpec.Builder builder = new CommentedConfigSpec.Builder();
builder.push("discord");
CLIENT_TOKEN = builder CLIENT_TOKEN = builder
.comment("The client secret/token for the bot user", "This must be set, or the application will not start up.") .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 OWNER_ID = builder
.comment("The id of the bot owner; used for sending status messages and for bot administration commands.", .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.") "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 STORAGE_PATH = builder
.comment("The folder where per-guild storage is kept.") .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 CUSTOM_TRANSLATION_FILE = builder
.comment("A file which contains custom translation keys to load for messages.", .comment("A file which contains custom translation keys to load for messages.",
"If blank, no file shall be loaded.") "If blank, no file shall be loaded.")
.define("messages.custom_translations", ""); .define("messages.custom_translations", "");
COMMAND_PREFIX = builder COMMAND_PREFIX = builder
.comment("The prefix for commands.") .comment("The prefix for commands.")
.define("commands.prefix", "!"); .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(); spec = builder.build();
this.configPath = options.getConfigPath().orElse(DEFAULT_CONFIG_PATH); this.configPath = options.getConfigPath().orElse(DEFAULT_CONFIG_PATH);

View File

@ -312,6 +312,33 @@ public class Messages {
embed.addField(translate("moderation.unwarn.field.reason"), entry.getReason(), false); embed.addField(translate("moderation.unwarn.field.reason"), entry.getReason(), false);
return channel.sendMessage(embed.build()); 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());
}
} }
} }

View File

@ -32,7 +32,7 @@ public class Translations {
loadTranslations(); loadTranslations();
} }
void loadTranslations() { public void loadTranslations() {
if (translationsFile == null) { if (translationsFile == null) {
JANITOR.info(TRANSLATIONS, "No translation file given, using default english translations"); JANITOR.info(TRANSLATIONS, "No translation file given, using default english translations");
loadDefaultTranslations(); loadDefaultTranslations();

View File

@ -72,5 +72,14 @@
"moderation.unwarn.cannot_unwarn_self.desc": "Performer cannot remove a warning from themselves.", "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.performer": "Performer/Original Target",
"moderation.unwarn.cannot_unwarn_self.field.original_performer": "Original Performer", "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"
} }