1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-11-12 21:31:26 +00:00

Refactor, remove translations, add guild config system

This commit is contained in:
Arnold Alejo Nunag 2020-11-12 01:17:09 +08:00 committed by sciwhiz12
parent df831da94d
commit 42adb8238c
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
52 changed files with 473 additions and 551 deletions

View File

@ -44,11 +44,6 @@ public class BotConsole {
case "reload": {
if (parts.length >= 2)
switch (parts[1]) {
case "translations": {
CONSOLE.info("Reloading translations");
bot.getTranslations().loadTranslations();
break outer;
}
case "messages": {
CONSOLE.info("Reloading messages");
bot.getMessages().loadMessages();

View File

@ -8,8 +8,8 @@ import net.dv8tion.jda.api.entities.PrivateChannel;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.config.ConfigManager;
import sciwhiz12.janitor.msg.Messages;
import sciwhiz12.janitor.msg.TranslationMap;
import sciwhiz12.janitor.msg.emote.ReactionManager;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import sciwhiz12.janitor.storage.GuildStorage;
@ -27,8 +27,8 @@ public class JanitorBot {
private final BotConsole console;
private final GuildStorage storage;
private final GuildStorage.SavingThread storageSavingThread;
private final ConfigManager configManager;
private final CommandRegistry cmdRegistry;
private final TranslationMap translations;
private final SubstitutionMap substitutions;
private final Messages messages;
private final ReactionManager reactions;
@ -38,10 +38,10 @@ public class JanitorBot {
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.translations = new TranslationMap(this, config.getTranslationsFile());
this.configManager = new ConfigManager(this, config.getConfigsFolder());
this.cmdRegistry = new CommandRegistry(this);
this.substitutions = new SubstitutionMap(this);
this.messages = new Messages(this, config.getTranslationsFile());
this.messages = new Messages(this, config.getMessagesFolder());
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);
@ -78,14 +78,12 @@ public class JanitorBot {
public GuildStorage getStorage() { return this.storage; }
public ConfigManager getConfigManager() { return configManager; }
public CommandRegistry getCommandRegistry() {
return this.cmdRegistry;
}
public TranslationMap getTranslations() {
return this.translations;
}
public ReactionManager getReactionManager() {
return this.reactions;
}
@ -113,6 +111,8 @@ public class JanitorBot {
discord.shutdown();
storageSavingThread.stopThread();
storage.save();
configManager.save();
configManager.close();
console.stop();
}

View File

@ -1,9 +1,10 @@
package sciwhiz12.janitor.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.config.GuildConfig;
import sciwhiz12.janitor.msg.Messages;
public abstract class BaseCommand {
@ -25,9 +26,11 @@ public abstract class BaseCommand {
return getBot().getMessages();
}
protected BotConfig config() {
return getBot().getConfig();
}
protected GuildConfig config(MessageReceivedEvent event) { return config(event.getGuild().getIdLong()); }
protected GuildConfig config(Guild guild) { return config(guild.getIdLong()); }
protected GuildConfig config(long guildID) { return getBot().getConfigManager().getConfig(guildID); }
public abstract LiteralArgumentBuilder<MessageReceivedEvent> getNode();
}

View File

@ -20,6 +20,7 @@ import sciwhiz12.janitor.commands.moderation.UnbanCommand;
import sciwhiz12.janitor.commands.moderation.UnwarnCommand;
import sciwhiz12.janitor.commands.moderation.WarnCommand;
import sciwhiz12.janitor.commands.moderation.WarnListCommand;
import sciwhiz12.janitor.config.GuildConfig;
import sciwhiz12.janitor.utils.Util;
import java.util.HashMap;
@ -30,13 +31,11 @@ import static sciwhiz12.janitor.Logging.JANITOR;
public class CommandRegistry implements EventListener {
private final JanitorBot bot;
private final String prefix;
private final Map<String, BaseCommand> registry = new HashMap<>();
private final CommandDispatcher<MessageReceivedEvent> dispatcher;
public CommandRegistry(JanitorBot bot, String prefix) {
public CommandRegistry(JanitorBot bot) {
this.bot = bot;
this.prefix = prefix;
this.dispatcher = new CommandDispatcher<>();
addCommand(new PingCommand(this, "ping", "Pong!"));
@ -69,17 +68,28 @@ public class CommandRegistry implements EventListener {
public void onEvent(@NotNull GenericEvent genericEvent) {
if (!(genericEvent instanceof MessageReceivedEvent)) return;
MessageReceivedEvent event = (MessageReceivedEvent) genericEvent;
if (event.getAuthor().isBot()) return;
final String prefix;
if (event.isFromGuild()) {
prefix = getBot().getConfigManager().getConfig(event.getGuild().getIdLong())
.forGuild(GuildConfig.COMMAND_PREFIX);
} else {
prefix = getBot().getConfig().getCommandPrefix();
}
String msg = event.getMessage().getContentRaw();
if (!msg.startsWith(this.prefix)) return;
if (!msg.startsWith(prefix)) return;
JANITOR.debug(COMMANDS, "Received message starting with valid command prefix. Author: {}, full message: {}",
Util.toString(event.getAuthor()), msg);
try {
StringReader command = new StringReader(msg.substring(this.prefix.length()));
StringReader command = new StringReader(msg.substring(prefix.length()));
ParseResults<MessageReceivedEvent> parseResults = this.dispatcher.parse(command, event);
if (parseResults.getReader().canRead()) {
// Parsing did not succeed, i.e. command not found
// TODO: add separate code path when insufficient permissions / requires fails
JANITOR.error(COMMANDS, "Error while parsing command: {}", parseResults.getExceptions().values());
if (parseResults.getExceptions().isEmpty()) {
JANITOR.info(COMMANDS, "Command not found.");
} else {
JANITOR.error(COMMANDS, "Error while parsing command: {}", parseResults.getExceptions().values());
}
return;
}
JANITOR.debug(COMMANDS, "Executing command.");

View File

@ -124,7 +124,7 @@ public class BanCommand extends BaseCommand {
.flatMap(v -> messages().getRegularMessage("moderation/ban/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("private_message", () -> res.isSuccess() ? "\u2705" : "\u274C")
.with("delete_duration", () -> String.valueOf(days))
.with("reason", () -> reason)
.send(getBot(), channel)

View File

@ -110,7 +110,7 @@ public class KickCommand extends BaseCommand {
.flatMap(v -> messages().getRegularMessage("moderation/kick/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("private_message", () -> res.isSuccess() ? "\u2705" : "\u274C")
.with("reason", () -> reason)
.send(getBot(), channel)
)

View File

@ -34,6 +34,8 @@ 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.config.GuildConfig.ENABLE_NOTES;
import static sciwhiz12.janitor.config.GuildConfig.MAX_NOTES_PER_MOD;
import static sciwhiz12.janitor.msg.MessageHelper.*;
public class NoteCommand extends BaseCommand {
@ -46,7 +48,7 @@ public class NoteCommand extends BaseCommand {
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("note")
.requires(ctx -> config().NOTES_ENABLE.get())
.requires(event -> config(event).forGuild(ENABLE_NOTES))
.then(literal("add")
.then(argument("target", member())
.then(argument("contents", greedyString())
@ -126,7 +128,7 @@ public class NoteCommand extends BaseCommand {
} else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get();
final int maxAmount = config(ctx.getSource()).forGuild(MAX_NOTES_PER_MOD);
if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
@ -201,13 +203,8 @@ public class NoteCommand extends BaseCommand {
} else {
messages().<Map.Entry<Integer, NoteEntry>>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)
.apply(noteEntry("note_entry", entry.getKey(), entry.getValue()))
)
.build(channel, getBot(), ctx.getSource().getMessage(),
NoteStorage.get(getBot().getStorage(), guild)

View File

@ -10,6 +10,7 @@ import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.GuildConfig;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
@ -20,6 +21,7 @@ import javax.annotation.Nullable;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
import static sciwhiz12.janitor.config.GuildConfig.*;
public class UnwarnCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
@ -31,7 +33,7 @@ public class UnwarnCommand extends BaseCommand {
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("unwarn")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.requires(ctx -> config(ctx).forGuild(ENABLE_WARNS))
.then(argument("caseId", IntegerArgumentType.integer(1))
.executes(this::run)
);
@ -73,13 +75,13 @@ public class UnwarnCommand extends BaseCommand {
.send(getBot(), channel).queue();
} else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) {
&& !config(guild).forGuild(ALLOW_REMOVE_SELF_WARNINGS)) {
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()
} else if (config(guild).forGuild(WARNS_RESPECT_MOD_ROLES)
&& (temp = guild.getMember(entry.getPerformer())) != null && !performer.canInteract(temp)) {
messages().getRegularMessage("moderation/error/unwarn/cannot_remove_higher_mod")
.apply(MessageHelper.member("performer", performer))

View File

@ -10,6 +10,7 @@ import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.GuildConfig;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
@ -26,6 +27,7 @@ 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.config.GuildConfig.ALLOW_WARN_OTHER_MODERATORS;
public class WarnCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
@ -37,7 +39,7 @@ public class WarnCommand extends BaseCommand {
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warn")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.requires(ctx -> config(ctx).forGuild(GuildConfig.ENABLE_WARNS))
.then(argument("member", member())
.then(argument("reason", greedyString())
.executes(ctx -> this.run(ctx, getString(ctx, "reason")))
@ -84,7 +86,7 @@ public class WarnCommand extends BaseCommand {
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) {
} else if (target.hasPermission(WARN_PERMISSION) && config(guild).forGuild(ALLOW_WARN_OTHER_MODERATORS)) {
messages().getRegularMessage("moderation/error/warn/cannot_warn_mods")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
@ -104,7 +106,7 @@ public class WarnCommand extends BaseCommand {
.flatMap(res -> messages().getRegularMessage("moderation/warn/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("private_message", () -> res.isSuccess() ? "\u2705" : "\u274C")
.send(getBot(), channel)
)
.queue();

View File

@ -26,8 +26,7 @@ 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;
import static sciwhiz12.janitor.config.GuildConfig.ENABLE_WARNS;
public class WarnListCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
@ -39,7 +38,7 @@ public class WarnListCommand extends BaseCommand {
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warnlist")
.requires(ctx -> getBot().getConfig().WARNINGS_ENABLE.get())
.requires(ctx -> config(ctx).forGuild(ENABLE_WARNS))
.then(literal("target")
.then(argument("target", member())
.then(literal("mod")
@ -100,13 +99,8 @@ public class WarnListCommand extends BaseCommand {
} else {
messages().<Map.Entry<Integer, WarningEntry>>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)
.setEntryApplier((entry, subs) ->
subs.apply(MessageHelper.warningEntry("warning_entry", entry.getKey(), entry.getValue()))
)
.build(channel, getBot(), ctx.getSource().getMessage(),
WarningStorage.get(getBot().getStorage(), guild)

View File

@ -19,22 +19,15 @@ public class BotConfig {
private final CommentedConfigSpec.ConfigValue<String> CLIENT_TOKEN;
private final CommentedConfigSpec.LongValue OWNER_ID;
public final CommentedConfigSpec.ConfigValue<String> CONFIGS_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_MESSAGES_DIRECTORY;
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;
public final CommentedConfigSpec.IntValue NOTES_MAX_AMOUNT_PER_MOD;
public final CommentedConfigSpec.BooleanValue NOTES_ENABLE;
private final BotOptions options;
private final Path configPath;
private final CommentedConfigSpec spec;
@ -55,6 +48,10 @@ public class BotConfig {
.defineInRange("owner_id", 0L, Long.MIN_VALUE, Long.MAX_VALUE);
builder.pop();
CONFIGS_PATH = builder
.comment("The folder where guild configs are kept.")
.define("configs_path", "configs");
builder.push("storage");
STORAGE_PATH = builder
.comment("The folder where per-guild storage is kept.")
@ -64,10 +61,6 @@ public class BotConfig {
.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", "");
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.")
@ -77,36 +70,6 @@ public class BotConfig {
.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.comment("Settings for the notes system").push("notes");
NOTES_ENABLE = builder
.comment("Whether to enable the notes system. If disabled, the related commands are force-disabled.")
.define("enable", true);
NOTES_MAX_AMOUNT_PER_MOD = builder
.comment("The max amount of notes for a user per moderator.")
.defineInRange("max_amount", Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
builder.pop();
}
builder.pop();
spec = builder.build();
this.configPath = options.getConfigPath().orElse(DEFAULT_CONFIG_PATH);
@ -129,15 +92,6 @@ public class BotConfig {
return config;
}
@Nullable
public Path getTranslationsFile() {
return options.getTranslationsFile().
or(() -> CUSTOM_TRANSLATION_FILE.get().isBlank() ?
Optional.empty() :
Optional.of(Path.of(CUSTOM_TRANSLATION_FILE.get())))
.orElse(null);
}
@Nullable
public Path getMessagesFolder() {
return options.getMessagesFolder().
@ -147,6 +101,15 @@ public class BotConfig {
.orElse(null);
}
@Nullable
public Path getConfigsFolder() {
return options.getConfigsFolder().
or(() -> CONFIGS_PATH.get().isBlank() ?
Optional.empty() :
Optional.of(Path.of(CONFIGS_PATH.get())))
.orElse(null);
}
public String getToken() {
return options.getToken().orElse(CLIENT_TOKEN.get());
}

View File

@ -13,8 +13,8 @@ import static joptsimple.util.PathProperties.*;
public class BotOptions {
private final OptionSet options;
private final ArgumentAcceptingOptionSpec<Path> configPath;
private final ArgumentAcceptingOptionSpec<Path> translationsPath;
private final ArgumentAcceptingOptionSpec<Path> messagesFolder;
private final ArgumentAcceptingOptionSpec<Path> configsFolder;
private final ArgumentAcceptingOptionSpec<String> token;
private final ArgumentAcceptingOptionSpec<String> prefix;
private final ArgumentAcceptingOptionSpec<Long> owner;
@ -25,12 +25,12 @@ public class BotOptions {
.accepts("config", "The path to the config file; defaults to 'config.toml'")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(FILE_EXISTING, READABLE, WRITABLE));
this.translationsPath = parser
.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")
.accepts("messages", "The path to the custom messages folder")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(DIRECTORY_EXISTING, READABLE));
this.configsFolder = parser
.accepts("guildConfigs", "The path to the guild configs folder")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(DIRECTORY_EXISTING, READABLE));
this.token = parser
@ -50,14 +50,14 @@ public class BotOptions {
return configPath.valueOptional(options);
}
public Optional<Path> getTranslationsFile() {
return translationsPath.valueOptional(options);
}
public Optional<Path> getMessagesFolder() {
return messagesFolder.valueOptional(options);
}
public Optional<Path> getConfigsFolder() {
return configsFolder.valueOptional(options);
}
public Optional<String> getToken() {
return token.valueOptional(options);
}

View File

@ -0,0 +1,44 @@
package sciwhiz12.janitor.config;
import sciwhiz12.janitor.JanitorBot;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ConfigManager {
private final JanitorBot bot;
private final Path configPath;
private final Map<Long, GuildConfig> configMap = new HashMap<>();
public ConfigManager(JanitorBot bot, Path configPath) {
this.bot = bot;
this.configPath = configPath;
}
public GuildConfig getConfig(long guildID) {
return configMap.computeIfAbsent(guildID, (id) -> new GuildConfig(id, getFile(guildID)));
}
public JanitorBot getBot() {
return bot;
}
public void save() {
configMap.values().forEach(GuildConfig::save);
}
public void close() {
configMap.values().forEach(GuildConfig::close);
for (Iterator<GuildConfig> iterator = configMap.values().iterator(); iterator.hasNext(); ) {
iterator.next().close();
iterator.remove();
}
}
private Path getFile(long guildID) {
final Path file = Path.of(Long.toHexString(guildID) + ".toml");
return configPath.resolve(file);
}
}

View File

@ -0,0 +1,183 @@
package sciwhiz12.janitor.config;
import com.electronwill.nightconfig.core.CommentedConfig;
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 com.google.common.base.Joiner;
import net.dv8tion.jda.api.entities.GuildChannel;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import static sciwhiz12.janitor.Logging.CONFIG;
import static sciwhiz12.janitor.Logging.JANITOR;
public class GuildConfig {
private static final Joiner NEWLINE = Joiner.on("\n");
private final long guild;
private final CommentedFileConfig config;
private boolean closed = false;
public static final ConfigNode<Boolean> ENABLE = new ConfigNode<>(
"enable", () -> true,
"Whether the bot is enabled for this guild.",
"Can be used to temporarily disable the bot in emergency situations.");
public static final ConfigNode<String> COMMAND_PREFIX = new ConfigNode<>(
"commands.prefix", () -> "!",
"The prefix for all commands.");
public static final ConfigNode<Boolean> ENABLE_WARNS = new ConfigNode<>(
"moderation.warns.enable", () -> true,
"Whether to enable the warnings system. If disabled, the related commands are force-disabled.");
public static final ConfigNode<Boolean> WARNS_RESPECT_MOD_ROLES = new ConfigNode<>(
"moderation.warns.respect_mod_roles", () -> false,
"Whether to prevent lower-ranked moderators (in the role hierarchy) from removing warnings " +
"issued by higher-ranked moderators.");
public static final ConfigNode<Boolean> ALLOW_WARN_OTHER_MODERATORS = new ConfigNode<>(
"moderation.warns.warn_other_moderators", () -> true,
"Whether to allow moderators to issue warnings against other moderators.");
public static final ConfigNode<Boolean> ALLOW_REMOVE_SELF_WARNINGS = new ConfigNode<>(
"moderation.warns.remove_self_warnings", () -> false,
"Whether to allow moderators to remove warnings from themselves.");
public static final ConfigNode<Boolean> ENABLE_NOTES = new ConfigNode<>(
"moderation.notes.enable", () -> true,
"Whether to enable the notes system. If disabled, the related commands are force-disabled.");
public static final ConfigNode<Integer> MAX_NOTES_PER_MOD = new ConfigNode<>(
"moderation.notes.max_amount", () -> Integer.MAX_VALUE,
"The max amount of notes for a user per moderator.");
GuildConfig(long guild, Path configPath) {
this.guild = guild;
this.config = CommentedFileConfig.builder(configPath, TomlFormat.instance())
.onFileNotFound(FileNotFoundAction.CREATE_EMPTY)
.preserveInsertionOrder()
.autosave()
.build();
try {
CONFIG.info("Building guild config for {} from {}", Long.toHexString(this.guild), configPath);
config.load();
FileWatcher.defaultInstance().addWatch(configPath, this::onFileChange);
ConfigNode.nodes.forEach(this::forGuild);
save();
} catch (IOException ex) {
JANITOR.error("Error while building config from file {}", configPath, ex);
}
}
protected CommentedConfig getChannelOverrides() {
final String channelOverridesID = "channel_overrides";
CommentedConfig channelConfigs = config.get(channelOverridesID);
if (channelConfigs == null) {
channelConfigs = config.createSubConfig();
config.set(channelOverridesID, channelConfigs);
config.setComment(channelOverridesID, "Channel overrides for certain configuration options");
}
return channelConfigs;
}
public CommentedConfig getChannelConfig(GuildChannel channel) {
final String id = channel.getId();
CommentedConfig overrides = getChannelOverrides();
CommentedConfig channelOverride = overrides.get(id);
if (channelOverride == null) {
channelOverride = overrides.createSubConfig();
overrides.set(id, channelOverride);
overrides.setComment(id, "Channel overrides for channel with name " + channel.getName());
}
return channelOverride;
}
private static void ensureComment(CommentedConfig config, String path, String expectedComment) {
if (!Objects.equals(config.getComment(path), expectedComment)) {
config.setComment(path, expectedComment);
}
}
public <T> T forGuild(ConfigNode<T> node) {
ensureComment(config, node.path, node.comment);
T value = config.get(node.path);
if (value == null) {
value = node.defaultValue.get();
config.set(node.path, value);
}
return value;
}
public <T> void forGuild(ConfigNode<T> node, T newValue) {
ensureComment(config, node.path, node.comment);
config.set(node.path, newValue);
}
public <T> T forChannel(GuildChannel channel, ConfigNode<T> node) {
CommentedConfig channelConfig = getChannelConfig(channel);
ensureComment(channelConfig, node.path, node.comment);
T value = channelConfig.getRaw(node.path);
if (value == null) {
value = node.defaultValue.get();
channelConfig.set(node.path, node.defaultValue.get());
}
return value;
}
public <T> void forChannel(GuildChannel channel, ConfigNode<T> node, T newValue) {
CommentedConfig channelConfig = getChannelConfig(channel);
ensureComment(channelConfig, node.path, node.comment);
channelConfig.set(node.path, newValue);
}
public long getGuildID() {
return guild;
}
public CommentedFileConfig getRawConfig() {
return config;
}
public void save() {
if (!closed) {
config.save();
}
}
public void close() {
if (!closed) {
closed = true;
config.close();
}
}
void onFileChange() {
if (closed) return;
try {
CONFIG.info("Reloading config due to file change {}", config.getNioPath());
config.load();
} catch (Exception ex) {
CONFIG.error("Error while reloading config from {}", config.getNioPath(), ex);
}
}
public static class ConfigNode<T> {
static final List<ConfigNode<?>> nodes = new ArrayList<>();
public final String path;
final String comment;
final Supplier<T> defaultValue;
ConfigNode(String path, Supplier<T> defaultValue, String... comment) {
this.path = path;
this.defaultValue = defaultValue;
this.comment = NEWLINE.join(comment);
nodes.add(this);
}
}
}

View File

@ -9,7 +9,7 @@ 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.ICustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import java.util.HashMap;
@ -20,10 +20,10 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<ListingMessageBuilder<T>> {
public class ListingMessageBuilder<T> implements ICustomSubstitutions<ListingMessageBuilder<T>> {
private final ListingMessage message;
private final Map<String, Supplier<String>> customSubstitutions;
private int amountPerPage = 10;
private int amountPerPage = 6;
private BiConsumer<T, CustomSubstitutions> entryApplier = (entry, sub) -> {};
public ListingMessageBuilder(ListingMessage message, Map<String, Supplier<String>> customSubstitutions) {
@ -55,22 +55,24 @@ public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<Listing
return this;
}
public void build(MessageChannel channel, TranslationMap translations, SubstitutionMap globalSubstitutions,
Message triggerMessage, List<T> entries) {
public void build(MessageChannel channel,
SubstitutionMap globalSubstitutions,
Message triggerMessage,
List<T> entries) {
final CustomSubstitutions customSubs = globalSubstitutions.with(customSubstitutions);
final ImmutableList<T> 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)
channel.sendMessage(pagedMessage.createMessage(customSubs, entryApplier))
.queue(listMsg -> globalSubstitutions.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))
pagedMessage.createMessage(customSubs, entryApplier))
)
.queue();
}
@ -88,7 +90,7 @@ public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<Listing
if (pagedMessage.advancePage(PageDirection.NEXT)) {
event.retrieveMessage()
.flatMap(eventMsg -> eventMsg.editMessage(
pagedMessage.createMessage(translations, customSubs, entryApplier))
pagedMessage.createMessage(customSubs, entryApplier))
)
.queue();
}
@ -98,7 +100,7 @@ public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<Listing
}
public void build(MessageChannel channel, JanitorBot bot, Message triggerMessage, List<T> entries) {
build(channel, bot.getTranslations(), bot.getSubstitutions(), triggerMessage, entries);
build(channel, bot.getSubstitutions(), triggerMessage, entries);
}
class PagedMessage {
@ -136,11 +138,10 @@ public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<Listing
return false;
}
public MessageEmbed createMessage(TranslationMap translations, CustomSubstitutions substitutions,
public MessageEmbed createMessage(CustomSubstitutions substitutions,
BiConsumer<T, CustomSubstitutions> 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)),

View File

@ -8,10 +8,11 @@ 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 sciwhiz12.janitor.msg.substitution.ICustomSubstitutions;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Objects;
import java.util.function.Consumer;
import static java.time.temporal.ChronoField.*;
@ -19,19 +20,19 @@ import static java.time.temporal.ChronoField.*;
public class MessageHelper {
private MessageHelper() {}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> snowflake(String head, ISnowflake snowflake) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> 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 <T extends IHasCustomSubstitutions<?>> Consumer<T> mentionable(String head, IMentionable mentionable) {
return builder -> builder
public static <T extends ICustomSubstitutions<?>> Consumer<T> mentionable(String head, IMentionable mentionable) {
return builder -> builder
.apply(snowflake(head, mentionable))
.with(head + ".mention", mentionable::getAsMention);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> role(String head, Role role) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> role(String head, Role role) {
return builder -> builder
.apply(mentionable(head, role))
.with(head + ".color_hex", () -> Integer.toHexString(role.getColorRaw()))
@ -39,7 +40,7 @@ public class MessageHelper {
.with(head + ".permissions", role.getPermissions()::toString);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> user(String head, User user) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> user(String head, User user) {
return builder -> builder
.apply(mentionable(head, user))
.with(head + ".name", user::getName)
@ -48,7 +49,7 @@ public class MessageHelper {
.with(head + ".flags", user.getFlags()::toString);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> guild(String head, Guild guild) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> guild(String head, Guild guild) {
return builder -> builder
.apply(snowflake(head, guild))
.with(head + ".name", guild::getName)
@ -58,10 +59,10 @@ public class MessageHelper {
.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);
.with(head + ".icon_url", () -> Objects.toString(guild.getIconUrl(), ""));
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> member(String head, Member member) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> member(String head, Member member) {
return builder -> builder
.apply(user(head, member.getUser()))
.apply(guild(head + ".guild", member.getGuild()))
@ -71,7 +72,7 @@ public class MessageHelper {
.with(head + ".color", () -> String.valueOf(member.getColorRaw()));
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> warningEntry(String head, int caseID, WarningEntry entry) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> warningEntry(String head, int caseID, WarningEntry entry) {
return builder -> builder
.with(head + ".case_id", () -> String.valueOf(caseID))
.apply(user(head + ".performer", entry.getPerformer()))
@ -80,7 +81,7 @@ public class MessageHelper {
.with(head + ".reason", entry::getReason);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> noteEntry(String head, int noteID, NoteEntry entry) {
public static <T extends ICustomSubstitutions<?>> Consumer<T> noteEntry(String head, int noteID, NoteEntry entry) {
return builder -> builder
.with(head + ".note_id", () -> String.valueOf(noteID))
.apply(user(head + ".performer", entry.getPerformer()))

View File

@ -127,8 +127,8 @@ public class Messages {
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.",
"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,

View File

@ -5,7 +5,7 @@ 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.ICustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import java.util.HashMap;
@ -13,7 +13,7 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class RegularMessageBuilder implements IHasCustomSubstitutions<RegularMessageBuilder> {
public class RegularMessageBuilder implements ICustomSubstitutions<RegularMessageBuilder> {
private final RegularMessage message;
private final Map<String, Supplier<String>> customSubstitutions;
@ -26,10 +26,6 @@ public class RegularMessageBuilder implements IHasCustomSubstitutions<RegularMes
this(message, new HashMap<>());
}
public RegularMessageBuilder(RegularMessageBuilder copy) {
this(copy.message, new HashMap<>(copy.customSubstitutions));
}
public RegularMessageBuilder apply(Consumer<RegularMessageBuilder> consumer) {
consumer.accept(this);
return this;
@ -40,12 +36,12 @@ public class RegularMessageBuilder implements IHasCustomSubstitutions<RegularMes
return this;
}
public MessageEmbed build(TranslationMap translations, SubstitutionMap substitutions) {
return message.create(translations, substitutions.with(customSubstitutions)).build();
public MessageEmbed build(SubstitutionMap substitutions) {
return message.create(substitutions.with(customSubstitutions)).build();
}
public MessageEmbed build(JanitorBot bot) {
return build(bot.getTranslations(), bot.getSubstitutions());
return build(bot.getSubstitutions());
}
public MessageAction send(JanitorBot bot, MessageChannel channel) {

View File

@ -1,84 +0,0 @@
package sciwhiz12.janitor.msg;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import sciwhiz12.janitor.JanitorBot;
import java.io.InputStreamReader;
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 TranslationMap {
public static final Pattern TRANSLATION_REGEX = Pattern.compile("<(.+?)>", CASE_INSENSITIVE);
private static final String DEFAULT_TRANSLATIONS_RESOURCE = "english.json";
private static final TypeReference<Map<String, String>> MAP_TYPE = new TypeReference<>() {};
private final JanitorBot bot;
private final Path translationsFile;
private final Map<String, String> translations = new HashMap<>();
private final ObjectMapper jsonMapper = new ObjectMapper();
public TranslationMap(JanitorBot bot, Path translationsFile) {
this.bot = bot;
this.translationsFile = translationsFile;
loadTranslations();
}
public void loadTranslations() {
if (translationsFile == null) {
JANITOR.info(TRANSLATIONS, "No translation file given, using default english translations");
loadDefaultTranslations();
return;
}
try {
JANITOR.debug(TRANSLATIONS, "Loading translations from file {}", translationsFile);
Map<String, String> 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) {
JANITOR.error(TRANSLATIONS, "Error while loading translations from file {}", translationsFile, e);
loadDefaultTranslations();
}
}
void loadDefaultTranslations() {
try {
JANITOR.debug(TRANSLATIONS, "Loading default english translations");
// noinspection UnstableApiUsage
Map<String, String> 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) {
JANITOR.error(TRANSLATIONS, "Error while loading default english translations", e);
}
}
public Map<String, String> getTranslationMap() {
return Collections.unmodifiableMap(translations);
}
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;
}
}

View File

@ -6,7 +6,6 @@ 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;
@ -142,27 +141,25 @@ public class ListingMessage {
}
public <T> EmbedBuilder create(
TranslationMap translations,
ISubstitutor global,
Iterable<T> iterable,
BiConsumer<T, CustomSubstitutions> entryApplier
) {
final Function<String, String> func = str -> str != null ? global.substitute(translations.translate(str)) : null;
final EmbedBuilder builder = new EmbedBuilder();
builder.setTitle(func.apply(title), func.apply(url));
builder.setTitle(global.substitute(title), global.substitute(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.setAuthor(global.substitute(authorName), global.substitute(authorUrl), global.substitute(authorIconUrl));
builder.setDescription(global.substitute(description));
builder.setImage(global.substitute(imageUrl));
builder.setThumbnail(global.substitute(thumbnailUrl));
builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
builder.setFooter(func.apply(footerText), func.apply(footerIconUrl));
builder.setFooter(global.substitute(footerText), global.substitute(footerIconUrl));
for (MessageEmbed.Field field : beforeFields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
builder.addField(global.substitute(field.getName()), global.substitute(field.getValue()), field.isInline());
}
final CustomSubstitutions entrySubs = new CustomSubstitutions();
final Function<String, String> entryFunc = str -> str != null ? entrySubs.substitute(func.apply(str)) : null;
final Function<String, String> entryFunc = str -> str != null ? entrySubs.substitute(global.substitute(str)) : null;
int count = 0;
for (T listEntry : iterable) {
entryApplier.accept(listEntry, entrySubs);
@ -181,11 +178,11 @@ public class ListingMessage {
count++;
}
if (count < 1) {
builder.getDescriptionBuilder().append(func.apply(emptyText));
builder.getDescriptionBuilder().append(global.substitute(emptyText));
}
for (MessageEmbed.Field field : afterFields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
builder.addField(global.substitute(field.getName()), global.substitute(field.getValue()), field.isInline());
}
return builder;
}

View File

@ -6,7 +6,6 @@ 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;
@ -16,7 +15,6 @@ 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)
@ -176,19 +174,18 @@ public class RegularMessage {
thumbnailUrl, fields);
}
public EmbedBuilder create(TranslationMap translations, ISubstitutor substitutions) {
final Function<String, String> func = str -> str != null ? substitutions.substitute(translations.translate(str)) : null;
public EmbedBuilder create(ISubstitutor subs) {
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.setTitle(subs.substitute(title), subs.substitute(url));
builder.setColor(parseColor(subs.substitute(color)));
builder.setAuthor(subs.substitute(authorName), subs.substitute(authorUrl), subs.substitute(authorIconUrl));
builder.setDescription(subs.substitute(description));
builder.setImage(subs.substitute(imageUrl));
builder.setThumbnail(subs.substitute(thumbnailUrl));
builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
builder.setFooter(func.apply(footerText), func.apply(footerIconUrl));
builder.setFooter(subs.substitute(footerText), subs.substitute(footerIconUrl));
for (MessageEmbed.Field field : fields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
builder.addField(subs.substitute(field.getName()), subs.substitute(field.getValue()), field.isInline());
}
return builder;
}

View File

@ -7,8 +7,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.annotation.Nullable;
public class CustomSubstitutions implements ISubstitutor, IHasCustomSubstitutions<CustomSubstitutions> {
public class CustomSubstitutions implements ISubstitutor, ICustomSubstitutions<CustomSubstitutions> {
private final Map<String, Supplier<String>> map;
public CustomSubstitutions(Map<String, Supplier<String>> map) {
@ -20,7 +21,8 @@ public class CustomSubstitutions implements ISubstitutor, IHasCustomSubstitution
}
@Override
public String substitute(String text) {
@Nullable
public String substitute(@Nullable String text) {
return SubstitutionMap.substitute(text, map);
}

View File

@ -3,7 +3,7 @@ package sciwhiz12.janitor.msg.substitution;
import java.util.function.Consumer;
import java.util.function.Supplier;
public interface IHasCustomSubstitutions<T extends IHasCustomSubstitutions<?>> {
public interface ICustomSubstitutions<T extends ICustomSubstitutions<?>> {
T with(String argument, Supplier<String> value);
T apply(Consumer<T> consumer);

View File

@ -1,5 +1,8 @@
package sciwhiz12.janitor.msg.substitution;
import javax.annotation.Nullable;
public interface ISubstitutor {
String substitute(String text);
@Nullable
String substitute(@Nullable String text);
}

View File

@ -11,8 +11,8 @@ import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT;
@ -20,18 +20,24 @@ 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<String, Supplier<String>> arguments) {
private static String quote(@Nullable String input) {
return input != null ? Matcher.quoteReplacement(input) : "";
}
@Nullable
public static String substitute(@Nullable String text, Map<String, Supplier<String>> arguments) {
if (text == null || text.isBlank()) return null;
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 quote(arguments.getOrDefault(
grp1,
() -> arguments.getOrDefault(nullMatcher.group(2), () -> nullMatcher.group(2)).get()
).get());
}
return quoteReplacement(arguments.getOrDefault(matchResult.group(1), () -> matchResult.group(0)).get());
return quote(arguments.getOrDefault(matchResult.group(1), () -> matchResult.group(0)).get());
});
}
@ -51,7 +57,8 @@ public class SubstitutionMap implements ISubstitutor {
return bot;
}
public String substitute(String text) {
@Nullable
public String substitute(@Nullable String text) {
return SubstitutionMap.substitute(text, defaultSubstitutions);
}

View File

@ -1,117 +0,0 @@
{
"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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.add.author": "Recorded note for user.",
"moderation.note.add.field.performer": "Performer",
"moderation.note.add.field.target": "Target",
"moderation.note.add.field.note_id": "Note ID",
"moderation.note.add.field.date_time": "Date & Time",
"moderation.note.add.field.contents": "Text",
"moderation.note.remove.author": "Removed note.",
"moderation.note.remove.field.performer": "Performer",
"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.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}"
}

View File

@ -1,5 +1,5 @@
{
"color": "${general.error.color}",
"title": "<general.error.ambiguous_member.title>",
"description": "<general.error.ambiguous_member.description>"
"title": "Ambiguous member argument!",
"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."
}

View File

@ -1,12 +1,4 @@
{
"color": "${general.error.color}",
"title": "<general.error.cannot_action_performer.title>",
"description": "<general.error.cannot_action_performer.description>",
"fields": [
{
"name": "<general.error.cannot_action_performer.field.performer>",
"value": "${performer.mention}",
"inline": true
}
]
"title": "Performer cannot act against self."
}

View File

@ -1,5 +1,4 @@
{
"color": "${general.error.color}",
"title": "<general.error.cannot_action_self.title>",
"description": "<general.error.cannot_action_self.description>"
"title": "Cannot perform this action against myself."
}

View File

@ -1,10 +1,9 @@
{
"color": "${general.error.color}",
"title": "<general.error.cannot_interact.title>",
"description": "<general.error.cannot_interact.description>",
"description": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.",
"fields": [
{
"name": "<general.error.cannot_interact.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
}

View File

@ -1,5 +1,4 @@
{
"color": "${general.error.color}",
"title": "<general.error.guild_only_command.title>",
"description": "<general.error.guild_only_command.description>"
"title": "Guild only command!"
}

View File

@ -1,10 +1,9 @@
{
"color": "${general.error.color}",
"title": "<general.error.insufficient_permissions.title>",
"description": "<general.error.insufficient_permissions.description>",
"description": "I do not have sufficient permissions to carry out this action.\nPlease contact your server administrators if you believe this is in error.",
"fields": [
{
"name": "<general.error.insufficient_permissions.field.permissions>",
"name": "Required permissions",
"value": "${required_permissions}",
"inline": true
}

View File

@ -4,16 +4,16 @@
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.ban.dm.title>",
"title": "You were banned from this server.",
"fields": [
{
"name": "<moderation.ban.dm.field.performer>",
"name": "Moderator",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.ban.dm.field.reason.name>",
"value": "<moderation.ban.dm.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;reason;_No reason specified._}",
"inline": true
}
]

View File

@ -1,33 +1,33 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.ban.info.author>",
"name": "Banned user from server.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.ban.info.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.ban.info.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.ban.info.field.private_message>",
"name": "Sent DM",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.ban.info.field.delete_duration.name>",
"value": "<moderation.ban.info.field.delete_duration.value>",
"name": "Message Deletion",
"value": "${delete_duration} day(s)",
"inline": true
},
{
"name": "<moderation.ban.info.field.reason.name>",
"value": "<moderation.ban.info.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;reason;_No reason specified._}",
"inline": false
}
]

View File

@ -1,15 +1,9 @@
{
"color": "${general.error.color}",
"title": "<moderation.cannot_interact.title>",
"description": "<moderation.cannot_interact.description>",
"description": "The performer of this command cannot moderate the target user, likely due to being lower in the role hierarchy.",
"fields": [
{
"name": "<moderation.cannot_interact.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.cannot_interact.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
}

View File

@ -1,15 +1,9 @@
{
"color": "${general.error.color}",
"title": "<moderation.insufficient_permissions.title>",
"description": "<moderation.insufficient_permissions.description>",
"description": "The performer of this command has insufficient permissions to use this command.",
"fields": [
{
"name": "<moderation.insufficient_permissions.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.insufficient_permissions.field.permissions>",
"name": "Required permissions",
"value": "${required_permissions}",
"inline": true
}

View File

@ -1,20 +1,14 @@
{
"color": "${general.error.color}",
"title": "<moderation.note.max_amount_of_notes.title>",
"description": "<moderation.note.max_amount_of_notes.description>",
"description": "The performer has reached the maximum amount of notes for the target user.",
"fields": [
{
"name": "<moderation.note.max_amount_of_notes.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.note.max_amount_of_notes.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.note.max_amount_of_notes.field.amount>",
"name": "(Max.) Amount",
"value": "${notes_amount}",
"inline": true
}

View File

@ -1,17 +1,4 @@
{
"color": "${general.error.color}",
"title": "<moderation.note.no_note_found.title>",
"description": "<moderation.note.no_note_found.description>",
"fields": [
{
"name": "<moderation.note.no_note_found.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.note.no_note_found.field.note_id>",
"value": "${note_id}",
"inline": true
}
]
"description": "No note with that note ID was found."
}

View File

@ -1,22 +1,16 @@
{
"color": "${general.error.color}",
"title": "<moderation.warn.cannot_remove_higher_mod.title>",
"description": "<moderation.warn.cannot_remove_higher_mod.description>",
"title": "Cannot remove warning issued by higher-ranked moderator.",
"fields": [
{
"name": "<moderation.warn.cannot_remove_higher_mod.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.cannot_remove_higher_mod.field.original_performer>",
"name": "Issuer",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.cannot_remove_higher_mod.field.case_id>",
"value": "${warning_entry.case_id}",
"inline": true
}
]
}

View File

@ -1,22 +1,16 @@
{
"color": "${general.error.color}",
"title": "<moderation.unwarn.cannot_unwarn_self.title>",
"description": "<moderation.unwarn.cannot_unwarn_self.description>",
"title": "Cannot remove warning from self.",
"fields": [
{
"name": "<moderation.unwarn.cannot_unwarn_self.field.performer>",
"name": "Performer/Target",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.cannot_unwarn_self.field.original_performer>",
"name": "Issuer",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.cannot_unwarn_self.field.case_id>",
"value": "${warning_entry.case_id}",
"inline": true
}
]
}

View File

@ -1,17 +1,4 @@
{
"color": "${general.error.color}",
"title": "<moderation.unwarn.no_case_found.title>",
"description": "<moderation.unwarn.no_case_found.description>",
"fields": [
{
"name": "<moderation.unwarn.no_case_found.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.no_case_found.field.case_id>",
"value": "${case_id}",
"inline": true
}
]
"description": "No warning with that case ID was found."
}

View File

@ -1,15 +1,9 @@
{
"color": "${general.error.color}",
"title": "<moderation.warn.cannot_warn_mods.title>",
"description": "<moderation.warn.cannot_warn_mods.description>",
"title": "Cannot warn other moderators.",
"fields": [
{
"name": "<moderation.warn.cannot_warn_mods.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.cannot_warn_mods.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
}

View File

@ -4,16 +4,16 @@
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.kick.dm.title>",
"title": "You were kicked from this server.",
"fields": [
{
"name": "<moderation.kick.dm.field.performer>",
"name": "Moderator",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.kick.dm.field.reason.name>",
"value": "<moderation.kick.dm.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;reason;_No reason specified._}",
"inline": true
}
]

View File

@ -1,28 +1,28 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.kick.info.author>",
"name": "Kicked user from server.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.kick.info.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.kick.info.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.kick.info.field.private_message>",
"name": "Sent DM",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.kick.info.field.reason.name>",
"value": "<moderation.kick.info.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;reason;_No reason specified._}",
"inline": false
}
]

View File

@ -1,32 +1,32 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.add.author>",
"name": "Recorded note for user.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.note.add.field.performer>",
"name": "Performer",
"value": "${note_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.note.add.field.target>",
"name": "Target",
"value": "${note_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.note.add.field.note_id>",
"name": "Note ID",
"value": "${note_entry.note_id}",
"inline": true
},
{
"name": "<moderation.note.add.field.date_time>",
"name": "Date & Time",
"value": "${note_entry.date_time}",
"inline": true
},
{
"name": "<moderation.note.add.field.contents>",
"name": "Text",
"value": "${note_entry.contents}",
"inline": false
}

View File

@ -2,12 +2,12 @@
"type": "listing",
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.list.author>",
"name": "Listing of Notes (Page ${page.current}/${page.max})",
"icon_url": "${moderation.icon_url}"
},
"entry": {
"type": "description",
"text": "<moderation.note.list.entry>"
"text": "**#${note_entry.note_id}**: ${note_entry.target.mention} by ${note_entry.performer.mention}\n - _${note_entry.date_time}_\n${note_entry.contents}"
},
"empty": "<moderation.note.list.empty>"
"empty": "**_No recorded notes matching your query._**"
}

View File

@ -1,37 +1,37 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.remove.author>",
"name": "Removed note.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.note.remove.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.original_performer>",
"name": "Original Performer",
"value": "${note_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.original_target>",
"name": "Original Target",
"value": "${note_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.note_id>",
"name": "Note ID",
"value": "${note_entry.note_id}",
"inline": true
},
{
"name": "<moderation.note.remove.field.date_time>",
"name": "Date & Time",
"value": "${note_entry.date_time}",
"inline": true
},
{
"name": "<moderation.note.remove.field.contents>",
"name": "Text",
"value": "${note_entry.contents}",
"inline": false
}

View File

@ -1,17 +1,17 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.unban.info.author>",
"name": "Unbanned user from server.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.unban.info.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unban.info.field.target>",
"name": "Target",
"value": "${target.mention}",
"inline": true
}

View File

@ -1,38 +1,38 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.unwarn.info.author>",
"name": "Removed warning from user.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.unwarn.info.field.performer>",
"name": "Performer",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.original_target>",
"name": "Original Target",
"value": "${warning_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.original_performer>",
"name": "Original Performer",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.case_id>",
"name": "Case ID",
"value": "${warning_entry.case_id}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.date_time>",
"name": "Date & Time",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.reason.name>",
"value": "<moderation.unwarn.info.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"inline": false
}
]

View File

@ -4,21 +4,21 @@
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.warn.dm.title>",
"title": "You were warned by a moderator.",
"fields": [
{
"name": "<moderation.warn.dm.field.performer>",
"name": "Moderator",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.dm.field.date_time>",
"name": "Date & Time",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.warn.dm.field.reason.name>",
"value": "<moderation.warn.dm.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"inline": false
}
]

View File

@ -1,38 +1,38 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.warn.info.author>",
"name": "Warned user.",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.warn.info.field.performer>",
"name": "Performer",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.info.field.target>",
"name": "Target",
"value": "${warning_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.warn.info.field.case_id>",
"name": "Case ID",
"value": "${warning_entry.case_id}",
"inline": true
},
{
"name": "<moderation.warn.info.field.private_message>",
"name": "Sent DM",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.warn.info.field.date_time>",
"name": "Date & Time",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.warn.info.field.reason.name>",
"value": "<moderation.warn.info.field.reason.value>",
"name": "Reason",
"value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"inline": false
}
]

View File

@ -2,12 +2,12 @@
"type": "listing",
"color": "${moderation.color}",
"author": {
"name": "<moderation.warnlist.author>",
"name": "Listing of Warnings (Page ${page.current}/${page.max})",
"icon_url": "${moderation.icon_url}"
},
"entry": {
"type": "description",
"text": "<moderation.warnlist.entry>"
"text": "**Case #${warning_entry.case_id}**: Warned ${warning_entry.target.mention} by ${warning_entry.performer.mention} \n - _Date & Time:_ ${warning_entry.date_time} \n - _Reason:_ ${warning_entry.reason}"
},
"empty": "<moderation.warnlist.empty>"
"empty": "**_No warnings logged matching your query._**"
}