1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-09-19 21:04:02 +00:00

Externalize messages to JSONs

For ref.: bot console has "reload messages" command to reload the messages from disk
This commit is contained in:
Arnold Alejo Nunag 2020-10-10 16:53:05 +08:00
parent 44a55d3962
commit 46eff3358e
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
51 changed files with 1663 additions and 851 deletions

View File

@ -49,6 +49,11 @@ public class BotConsole {
bot.getTranslations().loadTranslations();
break outer;
}
case "messages": {
CONSOLE.info("Reloading messages");
bot.getMessages().loadMessages();
break outer;
}
}
}
default:
@ -71,8 +76,7 @@ public class BotConsole {
while (!scanner.hasNextLine()) {
try {
Thread.sleep(150);
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
CONSOLE.warn("Console thread is interrupted");
continue outer;
}
@ -84,8 +88,7 @@ public class BotConsole {
}
CONSOLE.debug("Received command: {}", input);
BotConsole.this.parseCommand(input);
}
catch (Exception e) {
} catch (Exception e) {
CONSOLE.error("Error while running console thread", e);
}
}

View File

@ -9,8 +9,8 @@ import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.msg.Messages;
import sciwhiz12.janitor.msg.Substitutions;
import sciwhiz12.janitor.msg.Translations;
import sciwhiz12.janitor.msg.TranslationMap;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import sciwhiz12.janitor.utils.Util;
import java.nio.file.Path;
@ -22,13 +22,13 @@ import static sciwhiz12.janitor.Logging.STATUS;
public class JanitorBot {
private final JDA discord;
private final BotConfig config;
private final Messages messages;
private BotConsole console;
private final BotConsole console;
private final GuildStorage storage;
private final GuildStorage.SavingThread storageSavingThread;
private final CommandRegistry cmdRegistry;
private final Translations translations;
private final Substitutions substitutions;
private final TranslationMap translations;
private final SubstitutionMap substitutions;
private final Messages messages;
public JanitorBot(JDA discord, BotConfig config) {
this.config = config;
@ -36,9 +36,10 @@ public class JanitorBot {
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 Translations(this, config.getTranslationsFile());
this.messages = new Messages(this);
this.substitutions = new Substitutions(this);
this.translations = new TranslationMap(this, config.getTranslationsFile());
this.substitutions = new SubstitutionMap(this);
this.messages = new Messages(this, config.getTranslationsFile());
// TODO: find which of these can be loaded in parallel before the bot JDA is ready
discord.addEventListener(cmdRegistry);
discord.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(" n' sweeping n' testing!"));
discord.getGuilds().forEach(Guild::loadMembers);
@ -67,7 +68,9 @@ public class JanitorBot {
return this.config;
}
public Messages getMessages() { return this.messages; }
public Messages getMessages() {
return messages;
}
public GuildStorage getStorage() { return this.storage; }
@ -75,7 +78,7 @@ public class JanitorBot {
return this.cmdRegistry;
}
public Translations getTranslations() {
public TranslationMap getTranslations() {
return this.translations;
}
@ -105,7 +108,7 @@ public class JanitorBot {
console.stop();
}
public Substitutions getSubstitutions() {
public SubstitutionMap getSubstitutions() {
return substitutions;
}
}

View File

@ -9,6 +9,7 @@ public class Logging {
public static final Marker STATUS = MarkerFactory.getMarker("STATUS");
public static final Marker COMMANDS = MarkerFactory.getMarker("COMMANDS");
public static final Marker TRANSLATIONS = MarkerFactory.getMarker("TRANSLATIONS");
public static final Marker MESSAGES = MarkerFactory.getMarker("MESSAGES");
public static final Marker STORAGE = MarkerFactory.getMarker("STORAGE");
public static final Logger JANITOR = LoggerFactory.getLogger("janitor");

View File

@ -11,6 +11,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.List;
@ -60,45 +61,77 @@ public class BanCommand extends BaseCommand {
);
}
public int run(CommandContext<MessageReceivedEvent> ctx, int days, @Nullable String reason) throws CommandSyntaxException {
realRun(ctx, days, reason);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx, int days, @Nullable String reason) throws CommandSyntaxException {
int run(CommandContext<MessageReceivedEvent> ctx, int days, @Nullable String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
if (members.size() < 1) { return 1; }
final Member target = members.get(0);
if (guild.getSelfMember().equals(target))
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
else if (performer.equals(target))
channel.sendMessage(messages().GENERAL.cannotActionPerformer(performer).build(getBot())).queue();
else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION))
channel.sendMessage(messages().GENERAL.insufficientPermissions(performer, BAN_PERMISSION).build(getBot())).queue();
else if (!guild.getSelfMember().canInteract(target))
channel.sendMessage(messages().GENERAL.cannotInteract(performer, target).build(getBot())).queue();
else if (!performer.hasPermission(BAN_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, BAN_PERMISSION).build(getBot()))
.queue();
else if (!performer.canInteract(target))
channel.sendMessage(messages().MODERATION.ERRORS.cannotInteract(performer, target).build(getBot())).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", BAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().canInteract(target)) {
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(BAN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", BAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
target.getUser().openPrivateChannel()
.flatMap(dm -> dm.sendMessage(messages().MODERATION.bannedDM(performer, target, reason).build(getBot())))
.flatMap(dm -> messages().getRegularMessage("moderation/ban/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("reason", () -> reason)
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> ModerationHelper.banUser(target.getGuild(), performer, target, days, reason)
.flatMap(v -> channel.sendMessage(
messages().MODERATION.banUser(performer, target, reason, days, res.isSuccess()).build(getBot()))))
.flatMap(res ->
ModerationHelper.banUser(target.getGuild(), performer, target, days, reason)
.flatMap(v -> messages().getRegularMessage("moderation/ban/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("delete_duration", () -> String.valueOf(days))
.with("reason", () -> reason)
.send(getBot(), channel)
)
)
.queue();
}
return 1;
}
}

View File

@ -12,6 +12,7 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.CommandHelper;
import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.List;
@ -50,40 +51,72 @@ public class KickCommand extends BaseCommand {
private int runWithReason(CommandContext<MessageReceivedEvent> ctx, @Nullable String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) {
return 1;
}
if (members.size() < 1) { return 1; }
final Member target = members.get(0);
if (guild.getSelfMember().equals(target))
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
else if (performer.equals(target))
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION))
channel.sendMessage(messages().GENERAL.insufficientPermissions(performer, KICK_PERMISSION).build(getBot())).queue();
else if (!guild.getSelfMember().canInteract(target))
channel.sendMessage(messages().GENERAL.cannotInteract(performer, target).build(getBot())).queue();
else if (!performer.hasPermission(KICK_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, KICK_PERMISSION).build(getBot()))
.queue();
else if (!performer.canInteract(target))
channel.sendMessage(messages().MODERATION.ERRORS.cannotInteract(performer, target).build(getBot())).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", KICK_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().canInteract(target)) {
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(KICK_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", KICK_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
target.getUser().openPrivateChannel()
.flatMap(dm -> dm.sendMessage(messages().MODERATION.kickedDM(performer, target, reason).build(getBot())))
.flatMap(dm -> messages().getRegularMessage("moderation/kick/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("reason", () -> reason)
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> ModerationHelper.kickUser(target.getGuild(), performer, target, reason)
.flatMap(v -> channel.sendMessage(
messages().MODERATION.kickUser(performer, target, reason, res.isSuccess()).build(getBot()))
.flatMap(v -> messages().getRegularMessage("moderation/kick/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("reason", () -> reason)
.send(getBot(), channel)
)
)
.queue();
}
return 1;
}
}

View File

@ -1,6 +1,5 @@
package sciwhiz12.janitor.commands.moderation;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -13,6 +12,7 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.notes.NoteStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
@ -92,7 +92,10 @@ public class NoteCommand extends BaseCommand {
private int addNote(CommandContext<MessageReceivedEvent> ctx, String noteContents) throws CommandSyntaxException {
final MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
@ -102,24 +105,41 @@ public class NoteCommand extends BaseCommand {
final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target))
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
else if (performer.equals(target))
channel.sendMessage(messages().GENERAL.cannotActionPerformer(performer).build(getBot())).queue();
else if (!performer.hasPermission(NOTE_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, NOTE_PERMISSION).build(getBot()))
.queue();
else {
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get();
if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) {
channel.sendMessage(messages().MODERATION.ERRORS.maxAmountOfNotes(performer, target, maxAmount).build(getBot()))
.queue();
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("notes_amount", () -> String.valueOf(maxAmount))
.send(getBot(), channel).queue();
} else {
final NoteEntry entry = new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents);
int noteID = storage.addNote(entry);
channel.sendMessage(messages().MODERATION.addNote(performer, noteID, entry).build(getBot())).queue();
messages().getRegularMessage("moderation/note/add")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.noteEntry("note_entry", noteID, entry))
.send(getBot(), channel).queue();
}
}
return 1;
@ -133,7 +153,10 @@ public class NoteCommand extends BaseCommand {
throws CommandSyntaxException {
final MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
@ -145,7 +168,10 @@ public class NoteCommand extends BaseCommand {
if (members.size() < 1) return 1;
final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) {
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
return 1;
}
predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong());
@ -163,43 +189,62 @@ public class NoteCommand extends BaseCommand {
case NONE: {}
}
if (!performer.hasPermission(NOTE_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, NOTE_PERMISSION).build(getBot()))
.queue();
else
channel.sendMessage(messages().MODERATION.noteList(
NoteStorage.get(getBot().getStorage(), guild)
.getNotes()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).build(getBot())).queue();
if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
// channel.sendMessage(messages().MODERATION.noteList(
// NoteStorage.get(getBot().getStorage(), guild)
// .getNotes()
// .entrySet().stream()
// .filter(predicate)
// .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
// ).build(getBot())).queue();
messages().getRegularMessage("moderation/note/list")
.send(getBot(), channel).queue();
// TODO: fix this
}
return 1;
}
private int removeNote(CommandContext<MessageReceivedEvent> ctx, int noteID) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
if (!performer.hasPermission(NOTE_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, NOTE_PERMISSION).build(getBot()))
.queue();
else {
if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
@Nullable
final NoteEntry entry = storage.getNote(noteID);
if (entry == null)
channel.sendMessage(messages().MODERATION.ERRORS.noNoteFound(performer, noteID).build(getBot())).queue();
else {
if (entry == null) {
messages().getRegularMessage("moderation/note/add")
.apply(MessageHelper.member("performer", performer))
.with("note_id", () -> String.valueOf(noteID))
.send(getBot(), channel).queue();
} else {
storage.removeNote(noteID);
channel.sendMessage(messages().MODERATION.removeNote(performer, noteID, entry).build(getBot())).queue();
messages().getRegularMessage("moderation/note/remove")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.noteEntry("note_entry", noteID, entry))
.send(getBot(), channel).queue();
}
}
return 1;

View File

@ -12,6 +12,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.Locale;
@ -49,7 +50,10 @@ public class UnbanCommand extends BaseCommand {
void realNamedRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
@ -62,10 +66,14 @@ public class UnbanCommand extends BaseCommand {
.startsWith(username))
.collect(Collectors.toList()))
.queue(bans -> {
if (bans.size() > 1)
channel.sendMessage(messages().GENERAL.ambiguousMember(performer).build(getBot())).queue();
else if (bans.size() == 1)
if (bans.size() > 1) {
messages().getRegularMessage("general/error/ambiguous_member")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
} else if (bans.size() == 1) {
tryUnban(channel, guild, performer, bans.get(0).getUser());
}
});
}
@ -77,7 +85,10 @@ public class UnbanCommand extends BaseCommand {
void realIdRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
@ -97,16 +108,26 @@ public class UnbanCommand extends BaseCommand {
}
void tryUnban(MessageChannel channel, Guild guild, Member performer, User target) {
if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION))
channel.sendMessage(messages().GENERAL.insufficientPermissions(performer, UNBAN_PERMISSION).build(getBot()))
.queue();
else if (!performer.hasPermission(UNBAN_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, UNBAN_PERMISSION).build(getBot()))
.queue();
else
if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", UNBAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(UNBAN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", UNBAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
ModerationHelper.unbanUser(guild, target)
.flatMap(v -> channel.sendMessage(messages().MODERATION.unbanUser(performer, target).build(getBot())))
.flatMap(v -> messages().getRegularMessage("moderation/unban/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.user("target", target))
.send(getBot(), channel)
)
.queue();
}
}
}

View File

@ -12,6 +12,7 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.Objects;
@ -44,36 +45,54 @@ public class UnwarnCommand extends BaseCommand {
void realRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
int caseID = IntegerArgumentType.getInteger(ctx, "caseId");
if (!performer.hasPermission(WARN_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, WARN_PERMISSION).build(getBot()))
.queue();
else {
if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild);
@Nullable
final WarningEntry entry = storage.getWarning(caseID);
Member temp;
if (entry == null)
channel.sendMessage(messages().MODERATION.ERRORS.noWarnWithID(performer, caseID).build(getBot())).queue();
else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get())
channel.sendMessage(messages().MODERATION.ERRORS.cannotUnwarnSelf(performer, caseID, entry).build(getBot()))
.queue();
else if (config().WARNINGS_RESPECT_MOD_ROLES.get()
&& (temp = guild.getMember(entry.getPerformer())) != null
&& !performer.canInteract(temp))
channel.sendMessage(
messages().MODERATION.ERRORS.cannotRemoveHigherModerated(performer, caseID, entry).build(getBot())).queue();
else {
if (entry == null) {
messages().getRegularMessage("moderation/error/unwarn/no_case_found")
.apply(MessageHelper.member("performer", performer))
.with("case_id", () -> String.valueOf(caseID))
.send(getBot(), channel).queue();
} else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) {
messages().getRegularMessage("moderation/error/unwarn/cannot_unwarn_self")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
} else if (config().WARNINGS_RESPECT_MOD_ROLES.get()
&& (temp = guild.getMember(entry.getPerformer())) != null && !performer.canInteract(temp)) {
messages().getRegularMessage("moderation/error/unwarn/cannot_remove_higher_mod")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
} else {
storage.removeWarning(caseID);
channel.sendMessage(messages().MODERATION.unwarn(performer, caseID, entry).build(getBot())).queue();
messages().getRegularMessage("moderation/unwarn/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
}
}
}

View File

@ -12,6 +12,7 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
@ -44,48 +45,70 @@ public class WarnCommand extends BaseCommand {
);
}
public int run(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException {
realRun(ctx, reason);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException {
int run(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
if (members.size() < 1) { return 1; }
final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target))
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
else if (performer.equals(target))
channel.sendMessage(messages().GENERAL.cannotActionPerformer(performer).build(getBot())).queue();
else if (!performer.hasPermission(WARN_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, WARN_PERMISSION).build(getBot()))
.queue();
else if (!performer.canInteract(target))
channel.sendMessage(messages().MODERATION.ERRORS.cannotInteract(performer, target).build(getBot())).queue();
else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get())
channel.sendMessage(messages().MODERATION.ERRORS.cannotWarnMods(performer, target).build(getBot())).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) {
messages().getRegularMessage("moderation/error/warn/cannot_warn_mods")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
WarningEntry entry = new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason);
int caseId = WarningStorage.get(getBot().getStorage(), guild).addWarning(entry);
target.getUser().openPrivateChannel()
.flatMap(
dm -> dm.sendMessage(messages().MODERATION.warnedDM(performer, target, reason, dateTime).build(getBot())))
.flatMap(dm -> messages().getRegularMessage("moderation/warn/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> {
WarningEntry entry = new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason);
int caseId = WarningStorage.get(getBot().getStorage(), guild).addWarning(entry);
return channel
.sendMessage(messages().MODERATION.warnUser(performer, caseId, entry, res.isSuccess()).build(getBot()));
})
.flatMap(res -> messages().getRegularMessage("moderation/warn/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
.with("private_message", () -> res.isSuccess() ? "" : "")
.send(getBot(), channel)
)
.queue();
}
return 1;
}
}

View File

@ -1,6 +1,5 @@
package sciwhiz12.janitor.commands.moderation;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -12,7 +11,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.List;
@ -53,18 +52,15 @@ public class WarnListCommand extends BaseCommand {
.executes(ctx -> this.run(ctx, false, false));
}
public int run(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator)
throws CommandSyntaxException {
realRun(ctx, filterTarget, filterModerator);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator)
int run(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator)
throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
channel.sendMessage(messages().GENERAL.guildOnlyCommand(ctx.getSource().getAuthor()).build(getBot())).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
@ -72,33 +68,42 @@ public class WarnListCommand extends BaseCommand {
if (filterTarget) {
final List<Member> members = getMembers("target", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
if (members.size() < 1) return 1;
final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) {
channel.sendMessage(messages().GENERAL.cannotActionSelf(performer).build(getBot())).queue();
return;
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
return 1;
}
predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong());
}
if (filterModerator) {
final List<Member> members = getMembers("moderator", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
if (members.size() < 1) return 1;
final Member mod = members.get(0);
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == mod.getIdLong());
}
if (!performer.hasPermission(WARN_PERMISSION))
channel.sendMessage(
messages().MODERATION.ERRORS.performerInsufficientPermissions(performer, WARN_PERMISSION).build(getBot()))
.queue();
else
channel.sendMessage(messages().MODERATION.warnList(
WarningStorage.get(getBot().getStorage(), guild)
.getWarnings()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).build(getBot())).queue();
if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
// channel.sendMessage(messages().MODERATION.warnList(
// WarningStorage.get(getBot().getStorage(), guild)
// .getWarnings()
// .entrySet().stream()
// .filter(predicate)
// .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
// ).build(getBot())).queue();
messages().getRegularMessage("moderation/warn/list")
.send(getBot(), channel).queue();
// TODO: fix this
}
return 1;
}
}

View File

@ -7,9 +7,9 @@ import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import javax.annotation.Nullable;
import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT;
import static sciwhiz12.janitor.utils.Util.nameFor;
public class ModerationHelper {
@ -18,7 +18,7 @@ public class ModerationHelper {
auditReason.append("Kicked by ")
.append(nameFor(performer.getUser()))
.append(" on ")
.append(Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME));
.append(Instant.now().atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT));
if (reason != null)
auditReason.append(" for reason: ").append(reason);
return guild.kick(target, auditReason.toString());
@ -30,7 +30,7 @@ public class ModerationHelper {
auditReason.append("Banned by ")
.append(nameFor(performer.getUser()))
.append(" on ")
.append(Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME));
.append(Instant.now().atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT));
if (reason != null)
auditReason.append(" for reason: ").append(reason);
return guild.ban(target, deleteDuration, auditReason.toString());

View File

@ -23,6 +23,7 @@ public class BotConfig {
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;
@ -67,6 +68,10 @@ public class BotConfig {
.comment("A file which contains custom translation keys to load for messages.",
"If blank, no file shall be loaded.")
.define("messages.custom_translations", "");
CUSTOM_MESSAGES_DIRECTORY = builder
.comment("A folder containing custom messages, with a 'messages.json' key file.",
"If blank, no folder shall be loaded and defaults will be used.")
.define("messages.custom_messages", "");
COMMAND_PREFIX = builder
.comment("The prefix for commands.")
@ -115,8 +120,7 @@ public class BotConfig {
spec.setConfig(config);
// TODO: config spec
FileWatcher.defaultInstance().addWatch(configPath, this::onFileChange);
}
catch (IOException ex) {
} catch (IOException ex) {
JANITOR.error("Error while building config from file {}", configPath, ex);
}
}
@ -134,6 +138,15 @@ public class BotConfig {
.orElse(null);
}
@Nullable
public Path getMessagesFolder() {
return options.getMessagesFolder().
or(() -> CUSTOM_MESSAGES_DIRECTORY.get().isBlank() ?
Optional.empty() :
Optional.of(Path.of(CUSTOM_MESSAGES_DIRECTORY.get())))
.orElse(null);
}
public String getToken() {
return options.getToken().orElse(CLIENT_TOKEN.get());
}
@ -157,8 +170,7 @@ public class BotConfig {
CONFIG.info("Reloading config due to file change {}", configPath);
config.load();
spec.setConfig(config);
}
catch (Exception ex) {
} catch (Exception ex) {
CONFIG.error("Error while reloading config from {}", configPath, ex);
}
}

View File

@ -4,6 +4,7 @@ import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.util.PathConverter;
import joptsimple.util.PathProperties;
import java.nio.file.Path;
import java.util.Optional;
@ -14,6 +15,7 @@ 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<String> token;
private final ArgumentAcceptingOptionSpec<String> prefix;
private final ArgumentAcceptingOptionSpec<Long> owner;
@ -28,6 +30,10 @@ public class BotOptions {
.accepts("translations", "The path to the translations file")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(FILE_EXISTING, READABLE));
this.messagesFolder = parser
.accepts("translations", "The path to the custom messages folder")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(DIRECTORY_EXISTING, READABLE));
this.token = parser
.accepts("token", "The Discord token for the bot user")
.withRequiredArg();
@ -49,6 +55,10 @@ public class BotOptions {
return translationsPath.valueOptional(options);
}
public Optional<Path> getMessagesFolder() {
return messagesFolder.valueOptional(options);
}
public Optional<String> getToken() {
return token.valueOptional(options);
}

View File

@ -1,75 +0,0 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import java.util.EnumSet;
import java.util.stream.Collectors;
public final class General {
private final Messages messages;
General(Messages messages) {
this.messages = messages;
}
public MessageBuilder guildOnlyCommand(final User performer) {
return messages.failure()
.apply(builder -> messages.user(builder, "performer", performer))
.embed(embed -> embed
.setTitle("general.guild_only_command.title")
.setDescription("general.guild_only_command.description")
);
}
public MessageBuilder ambiguousMember(final Member performer) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.embed(embed -> embed
.setTitle("general.ambiguous_member.title")
.setDescription("general.ambiguous_member.description")
);
}
public MessageBuilder insufficientPermissions(final Member performer, final EnumSet<Permission> permissions) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.with("required_permissions", () -> permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")))
.embed(embed -> embed
.setTitle("general.insufficient_permissions.title")
.setDescription("general.insufficient_permissions.description")
)
.field("general.insufficient_permissions.field.permissions", true);
}
public MessageBuilder cannotInteract(final Member performer, final Member target) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.embed(embed -> embed
.setTitle("general.cannot_interact.title")
.setDescription("general.cannot_interact.description")
)
.field("general.cannot_interact.field.target", true);
}
public MessageBuilder cannotActionSelf(final Member performer) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.embed(embed -> embed
.setTitle("general.cannot_action_self.title")
.setDescription("general.cannot_action_self.description")
);
}
public MessageBuilder cannotActionPerformer(final Member performer) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.embed(embed -> embed
.setTitle("general.cannot_action_performer.title")
.setDescription("general.cannot_action_performer.description")
)
.field("general.cannot_action_performer.field.performer", true);
}
}

View File

@ -0,0 +1,110 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.function.Consumer;
import static java.time.temporal.ChronoField.*;
public class MessageHelper {
private MessageHelper() {}
public static Consumer<RegularMessageBuilder> snowflake(String head, ISnowflake snowflake) {
return builder -> builder
.with(head + ".id", snowflake::getId)
.with(head + ".creation_datetime", () -> snowflake.getTimeCreated().format(DATE_TIME_FORMAT));
}
public static Consumer<RegularMessageBuilder> mentionable(String head, IMentionable mentionable) {
return builder -> builder
.apply(snowflake(head, mentionable))
.with(head + ".mention", mentionable::getAsMention);
}
public static Consumer<RegularMessageBuilder> role(String head, Role role) {
return builder -> builder
.apply(mentionable(head, role))
.with(head + ".color_hex", () -> Integer.toHexString(role.getColorRaw()))
.with(head + ".name", role::getName)
.with(head + ".permissions", role.getPermissions()::toString);
}
public static Consumer<RegularMessageBuilder> user(String head, User user) {
return builder -> builder
.apply(mentionable(head, user))
.with(head + ".name", user::getName)
.with(head + ".discriminator", user::getDiscriminator)
.with(head + ".tag", user::getAsTag)
.with(head + ".flags", user.getFlags()::toString);
}
public static Consumer<RegularMessageBuilder> guild(String head, Guild guild) {
return builder -> builder
.apply(snowflake(head, guild))
.with(head + ".name", guild::getName)
.with(head + ".description", guild::getDescription)
.with(head + ".voice_region", guild.getRegion()::toString)
.with(head + ".boost.tier", guild.getBoostTier()::toString)
.with(head + ".boost.count", () -> String.valueOf(guild.getBoostCount()))
.with(head + ".locale", guild.getLocale()::toString)
.with(head + ".verification_level", guild.getVerificationLevel()::toString)
.with(head + ".icon_url", guild::getIconUrl);
}
public static Consumer<RegularMessageBuilder> member(String head, Member member) {
return builder -> builder
.apply(user(head, member.getUser()))
.apply(guild(head + ".guild", member.getGuild()))
.with(head + ".nickname", member::getNickname)
.with(head + ".effective_name", member::getEffectiveName)
.with(head + ".join_datetime", () -> member.getTimeJoined().format(DATE_TIME_FORMAT))
.with(head + ".color", () -> String.valueOf(member.getColorRaw()));
}
public static Consumer<RegularMessageBuilder> warningEntry(String head, int caseID, WarningEntry entry) {
return builder -> builder
.with(head + ".case_id", () -> String.valueOf(caseID))
.apply(user(head + ".performer", entry.getPerformer()))
.apply(user(head + ".target", entry.getWarned()))
.with(head + ".date_time", () -> entry.getDateTime().format(DATE_TIME_FORMAT))
.with(head + ".reason", entry::getReason);
}
public static Consumer<RegularMessageBuilder> noteEntry(String head, int noteID, NoteEntry entry) {
return builder -> builder
.with(head + ".note_id", () -> String.valueOf(noteID))
.apply(user(head + ".performer", entry.getPerformer()))
.apply(user(head + ".target", entry.getTarget()))
.with(head + ".date_time", () -> entry.getDateTime().format(DATE_TIME_FORMAT))
.with(head + ".contents", entry::getContents);
}
public static final DateTimeFormatter DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseLenient()
.appendValue(YEAR, 4) // 2 digit year not handled
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.appendLiteral(' ')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalEnd()
.appendLiteral(' ')
.appendOffset("+HHMM", "GMT")
.toFormatter();
}

View File

@ -1,97 +1,119 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.dv8tion.jda.api.entities.MessageEmbed;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.msg.json.RegularMessage;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import static com.google.common.io.Resources.getResource;
import static sciwhiz12.janitor.Logging.JANITOR;
import static sciwhiz12.janitor.Logging.MESSAGES;
public class Messages {
public static final int FAILURE_COLOR = 0xF73132;
public static final String JSON_FILE_SUFFIX = ".json";
public static final String MESSAGES_FILENAME = "messages";
public static final String DEFAULT_MESSAGES_FOLDER = "messages/";
public static final TypeReference<List<String>> LIST_TYPE = new TypeReference<>() {};
private final JanitorBot bot;
public final General GENERAL;
public final Moderation MODERATION;
private final Path messagesFolder;
private final Map<String, RegularMessage> regularMessages = new HashMap<>();
private final ObjectMapper jsonMapper = new ObjectMapper();
public Messages(JanitorBot bot) {
public Messages(JanitorBot bot, Path messagesFolder) {
this.bot = bot;
this.GENERAL = new General(this);
this.MODERATION = new Moderation(this);
this.messagesFolder = messagesFolder;
loadMessages();
}
public JanitorBot getBot() {
return bot;
public void loadMessages() {
boolean success = false;
if (messagesFolder != null) {
JANITOR.debug(MESSAGES, "Loading messages from folder {}", messagesFolder);
success = loadMessages(
path -> Files.newBufferedReader(messagesFolder.resolve(path + JSON_FILE_SUFFIX))
);
} else {
JANITOR.info(MESSAGES, "No custom messages folder specified");
}
if (!success) {
JANITOR.info(MESSAGES, "Loading default messages");
//noinspection UnstableApiUsage
loadMessages(
file -> new InputStreamReader(getResource(DEFAULT_MESSAGES_FOLDER + file + JSON_FILE_SUFFIX).openStream())
);
}
}
public MessageBuilder message() {
final MessageBuilder builder = new MessageBuilder();
builder.embed()
.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
return builder;
boolean loadMessages(FileOpener files) {
try (Reader keyReader = files.open(MESSAGES_FILENAME)) {
List<String> keysList = jsonMapper.readValue(keyReader, LIST_TYPE);
regularMessages.clear();
for (String messageKey : keysList) {
final String path = messageKey.replace("/", FileSystems.getDefault().getSeparator());
try (Reader reader = files.open(path)) {
final JsonNode tree = jsonMapper.readTree(reader);
if ("regular".equals(tree.path("type").asText("regular"))) {
regularMessages.put(messageKey, jsonMapper.convertValue(tree, RegularMessage.class));
} else {
JANITOR.warn(MESSAGES, "Unknown message type {} for {}", tree.path("type").asText(), messageKey);
}
} catch (Exception e) {
JANITOR.error(MESSAGES, "Error while loading message {}", path, e);
}
}
JANITOR.info(MESSAGES, "Loaded {} messages", regularMessages.size());
return true;
} catch (Exception e) {
JANITOR.error(MESSAGES, "Error while loading messages", e);
return false;
}
}
public MessageBuilder failure() {
final MessageBuilder builder = message();
builder.embed()
.setColor(FAILURE_COLOR);
return builder;
public Map<String, RegularMessage> getRegularMessages() {
return Collections.unmodifiableMap(regularMessages);
}
public MessageBuilder snowflake(MessageBuilder builder, String head, ISnowflake snowflake) {
return builder
.with(head + ".id", snowflake::getId)
.with(head + ".creation_datetime", () -> snowflake.getTimeCreated().format(RFC_1123_DATE_TIME));
public RegularMessageBuilder getRegularMessage(String messageKey) {
final RegularMessage msg = regularMessages.get(messageKey);
if (msg == null) {
JANITOR.warn(MESSAGES, "Attempted to get unknown message with key {}", messageKey);
return new RegularMessageBuilder(UNKNOWN_MESSAGE).with("key", () -> messageKey);
}
return new RegularMessageBuilder(msg);
}
public MessageBuilder mentionable(MessageBuilder builder, String head, IMentionable mentionable) {
return builder
.apply(b -> snowflake(b, head, mentionable))
.with(head + ".mention", mentionable::getAsMention);
interface FileOpener {
Reader open(String filePath) throws IOException;
}
public MessageBuilder role(MessageBuilder builder, String head, Role role) {
return builder
.apply(b -> mentionable(b, head, role))
.with(head + ".color_hex", () -> Integer.toHexString(role.getColorRaw()))
.with(head + ".name", role::getName)
.with(head + ".permissions", role.getPermissions()::toString);
}
public MessageBuilder user(MessageBuilder builder, String head, User user) {
return builder
.apply(b -> mentionable(b, head, user))
.with(head + ".name", user::getName)
.with(head + ".discriminator", user::getDiscriminator)
.with(head + ".tag", user::getAsTag)
.with(head + ".flags", user.getFlags()::toString);
}
public MessageBuilder guild(MessageBuilder builder, String head, Guild guild) {
return builder
.apply(b -> snowflake(b, head, guild))
.with(head + ".name", guild::getName)
.with(head + ".description", guild::getDescription)
.with(head + ".voice_region", guild.getRegion()::toString)
.with(head + ".boost.tier", guild.getBoostTier()::toString)
.with(head + ".boost.count", () -> String.valueOf(guild.getBoostCount()))
.with(head + ".locale", guild.getLocale()::toString)
.with(head + ".verification_level", guild.getVerificationLevel()::toString);
}
public MessageBuilder member(MessageBuilder builder, String head, Member member) {
return builder
.apply(b -> user(b, head, member.getUser()))
.apply(b -> guild(b, head + ".guild", member.getGuild()))
.with(head + ".nickname", member::getNickname)
.with(head + ".effective_name", member::getEffectiveName)
.with(head + ".join_datetime", () -> member.getTimeJoined().format(RFC_1123_DATE_TIME))
.with(head + ".color", () -> String.valueOf(member.getColorRaw()));
}
public static final RegularMessage UNKNOWN_MESSAGE = new RegularMessage(
"UNKNOWN MESSAGE!",
null,
"A message was tried to be looked up, but was not found. Please report this to your bot maintainer/administrator.",
String.valueOf(0xFF0000),
null,
null,
null,
null,
null,
null,
null,
Collections.singletonList(new MessageEmbed.Field("Message Key", "${key}", false))
);
}

View File

@ -1,333 +0,0 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.EnumSet;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
public final class Moderation {
public static final int MODERATION_COLOR = 0xF1BD25;
public static final String GAVEL_ICON_URL = "https://cdn.discordapp.com/attachments/738478941760782526" +
"/760463743330549760/gavel.png";
private final Messages messages;
public final Errors ERRORS;
Moderation(Messages messages) {
this.messages = messages;
ERRORS = new Errors();
}
public MessageBuilder moderation() {
return messages.message()
.embed(embed -> embed
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC))
);
}
public MessageBuilder moderation(String author) {
return moderation()
.embed(embed -> embed.setAuthor(author, null, GAVEL_ICON_URL));
}
public class Errors {
private Errors() {}
public MessageBuilder performerInsufficientPermissions(final Member performer, final EnumSet<Permission> permissions) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.with("required_permissions",
() -> permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")))
.embed(embed -> embed
.setTitle("moderation.insufficient_permissions.title")
.setDescription("moderation.insufficient_permissions.description")
)
.field("moderation.insufficient_permissions.field.performer", true)
.field("moderation.insufficient_permissions.field.required_permissions", true);
}
public MessageBuilder cannotInteract(final Member performer, final Member target) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.embed(embed -> embed
.setTitle("moderation.cannot_interact.title")
.setDescription("moderation.cannot_interact.description")
)
.field("moderation.cannot_interact.field.performer", true)
.field("moderation.cannot_interact.field.target", true);
}
public MessageBuilder cannotWarnMods(final Member performer, final Member target) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.embed(embed -> embed
.setTitle("moderation.warn.cannot_warn_mods.title")
.setDescription("moderation.warn.cannot_warn_mods.description")
)
.field("moderation.warn.cannot_warn_mods.field.performer", true)
.field("moderation.warn.cannot_warn_mods.field.target", true);
}
public MessageBuilder cannotRemoveHigherModerated(final Member performer, final int caseID, final WarningEntry entry) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> warningEntry(builder, "warning_entry", caseID, entry))
.embed(embed -> embed
.setTitle("moderation.unwarn.cannot_remove_higher_mod.title")
.setDescription("moderation.unwarn.cannot_remove_higher_mod.description")
)
.field("moderation.unwarn.cannot_remove_higher_mod.field.performer", true)
.field("moderation.unwarn.cannot_remove_higher_mod.field.target", true);
}
public MessageBuilder maxAmountOfNotes(final Member performer, final Member target, final int amount) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("notes_amount", () -> String.valueOf(amount))
.embed(embed -> embed
.setTitle("moderation.note.max_amount_of_notes.title")
.setDescription("moderation.note.max_amount_of_notes.description")
)
.field("moderation.note.max_amount_of_notes.field.performer", true)
.field("moderation.note.max_amount_of_notes.field.target", true)
.field("moderation.note.max_amount_of_notes.field.amount", true);
}
public MessageBuilder noNoteFound(final Member performer, final int noteID) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.with("note_id", () -> String.valueOf(noteID))
.embed(embed -> embed
.setTitle("moderation.note.no_note_found.title")
.setDescription("moderation.note.no_note_found.description")
)
.field("moderation.note.no_note_found.field.performer", true)
.field("moderation.note.no_note_found.field.note_id", true);
}
public MessageBuilder noWarnWithID(final Member performer, final int caseID) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.with("case_id", () -> String.valueOf(caseID))
.embed(embed -> embed
.setTitle("moderation.unwarn.no_case_found.title")
.setDescription("moderation.unwarn.no_case_found.description")
)
.field("moderation.unwarn.no_case_found.field.performer", true)
.field("moderation.unwarn.no_case_found.field.note_id", true);
}
public MessageBuilder cannotUnwarnSelf(final Member performer, final int caseID, final WarningEntry entry) {
return messages.failure()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> warningEntry(builder, "warning_entry", caseID, entry))
.embed(embed -> embed
.setTitle("moderation.unwarn.cannot_unwarn_self.title")
.setDescription("moderation.unwarn.cannot_unwarn_self.description")
)
.field("moderation.unwarn.cannot_unwarn_self.field.performer", true)
.field("moderation.unwarn.cannot_unwarn_self.field.original_performer", true)
.field("moderation.unwarn.cannot_unwarn_self.field.target", true);
}
}
public MessageBuilder kickUser(final Member performer, final Member target, final @Nullable String reason,
final boolean sentDM) {
return moderation("moderation.kick.info.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("reason", () -> reason)
.field("moderation.kick.info.field.performer", true)
.field("moderation.kick.info.field.target", true)
.field("moderation.kick.info.field.private_message." + (sentDM ? "sent" : "unsent"), true)
.field("moderation.kick.info.field.reason", true);
}
public MessageBuilder kickedDM(final Member performer, final Member target, final @Nullable String reason) {
return moderation()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("reason", () -> reason)
.embed(embed -> embed
.setTitle("moderation.kick.dm.title")
.setAuthor("moderation.kick.dm.author", null, performer.getGuild().getIconUrl())
)
.field("moderation.kick.dm.field.performer", true)
.field("moderation.kick.dm.field.reason", true);
}
public MessageBuilder banUser(final Member performer, final Member target, final @Nullable String reason,
final int deletionDays, final boolean sentDM) {
return moderation("moderation.ban.info.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("delete_duration", () -> String.valueOf(deletionDays))
.with("reason", () -> reason)
.field("moderation.ban.info.field.performer", true)
.field("moderation.ban.info.field.target", true)
.field("moderation.ban.info.field.private_message." + (sentDM ? "sent" : "unsent"), true)
.field("moderation.ban.info.field.delete_duration", true)
.field("moderation.ban.info.field.reason", true);
}
public MessageBuilder bannedDM(final Member performer, final Member target, @Nullable final String reason) {
return moderation()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("reason", () -> reason)
.embed(embed -> embed
.setTitle("moderation.ban.dm.title")
.setAuthor("moderation.ban.dm.author", null, performer.getGuild().getIconUrl())
)
.field("moderation.ban.dm.field.performer", true)
.field("moderation.ban.dm.field.reason", true);
}
public MessageBuilder unbanUser(final Member performer, final User target) {
return moderation("moderation.unban.info.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.user(builder, "target", target))
.field("moderation.unban.info.field.performer", true)
.field("moderation.unban.info.field.target", true);
}
public void warningEntry(MessageBuilder builder, String head, int caseID, WarningEntry entry) {
builder
.with(head + ".case_id", () -> String.valueOf(caseID))
.apply(b -> messages.user(b, head + ".performer", entry.getPerformer()))
.apply(b -> messages.user(b, head + ".target", entry.getWarned()))
.with(head + ".date_time", () -> entry.getDateTime().format(RFC_1123_DATE_TIME))
.with(head + ".reason", entry::getReason);
}
public MessageBuilder warnUser(final Member performer, final int caseID, final WarningEntry entry, final boolean sentDM) {
return moderation("moderation.warn.info.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> warningEntry(builder, "warning_entry", caseID, entry))
.field("moderation.warn.info.field.performer", true)
.field("moderation.warn.info.field.target", true)
.field("moderation.warn.info.field.private_message." + (sentDM ? "sent" : "unsent"), true)
.field("moderation.warn.info.field.date_time", true)
.field("moderation.warn.info.field.case_id", true)
.field("moderation.warn.info.field.reason", true);
}
public MessageBuilder warnedDM(final Member performer, final Member target, final String reason,
final OffsetDateTime dateTime) {
return moderation()
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> messages.member(builder, "target", target))
.with("date_time", () -> dateTime.format(RFC_1123_DATE_TIME))
.with("reason", () -> reason)
.embed(embed -> embed
.setTitle("moderation.warn.dm.title")
.setAuthor("moderation.warn.dm.author", null, performer.getGuild().getIconUrl())
)
.field("moderation.warn.dm.field.performer", true)
.field("moderation.warn.dm.field.date_time", true)
.field("moderation.warn.dm.field.reason", true);
}
public MessageBuilder warnList(final Map<Integer, WarningEntry> displayWarnings) {
// return channel.sendMessage(
// moderationEmbed(translate("moderation.warnlist.author"))
// .setDescription(displayWarnings.size() > 0 ? displayWarnings.entrySet().stream()
// .sorted(Collections.reverseOrder(Comparator.comparingInt(Map.Entry::getKey)))
// .limit(10)
// .map(entry ->
// translate("moderation.warnlist.entry",
// entry.getKey(),
// entry.getValue().getWarned().getAsMention(),
// entry.getValue().getPerformer().getAsMention(),
// entry.getValue().getDateTime().format(RFC_1123_DATE_TIME),
// entry.getValue().getReason() != null
// ? entry.getValue().getReason()
// : translate("moderation.warnlist.entry.no_reason"))
// )
// .collect(Collectors.joining("\n"))
// : translate("moderation.warnlist.empty"))
// .build()
// );
return moderation()
.embed(embed -> embed.setTitle("NO OP, CURRENTLY IN PROGRESS"));
}
public MessageBuilder unwarn(final Member performer, final int caseID, final WarningEntry entry) {
return moderation("moderation.unwarn.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> warningEntry(builder, "warning_entry", caseID, entry))
.field("moderation.unwarn.field.performer", true)
.field("moderation.unwarn.field.case_id", true)
.field("moderation.unwarn.field.original_performer", true)
.field("moderation.unwarn.field.original_target", true)
.field("moderation.unwarn.field.date_time", true)
.field("moderation.unwarn.field.reason", true);
}
public void noteEntry(MessageBuilder builder, String head, int noteID, NoteEntry entry) {
builder
.with(head + ".note_id", () -> String.valueOf(noteID))
.apply(b -> messages.user(b, head + ".performer", entry.getPerformer()))
.apply(b -> messages.user(b, head + ".target", entry.getTarget()))
.with(head + ".date_time", () -> entry.getDateTime().format(RFC_1123_DATE_TIME))
.with(head + ".contents", entry::getContents);
}
public MessageBuilder addNote(final Member performer, final int noteID, final NoteEntry entry) {
return moderation("moderation.note.add.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> noteEntry(builder, "note", noteID, entry))
.field("moderation.note.add.field.performer", true)
.field("moderation.note.add.field.target", true)
.field("moderation.note.add.field.note_id", true)
.field("moderation.note.add.field.date_time", true)
.field("moderation.note.add.field.contents", true);
}
public MessageBuilder noteList(final Map<Integer, NoteEntry> displayNotes) {
// return channel.sendMessage(moderationEmbed(translate("moderation.note.list.author"))
// .setDescription(displayNotes.size() > 0 ? displayNotes.entrySet().stream()
// .sorted(Collections.reverseOrder(Comparator.comparingInt(Map.Entry::getKey)))
// .limit(10)
// .map(entry ->
// translate("moderation.note.list.entry",
// entry.getKey(),
// entry.getValue().getTarget().getAsMention(),
// entry.getValue().getPerformer().getAsMention(),
// entry.getValue().getDateTime().format(RFC_1123_DATE_TIME),
// entry.getValue().getContents())
// )
// .collect(Collectors.joining("\n"))
// : translate("moderation.note.list.empty"))
// .build()
// );
return moderation()
.embed(embed -> embed.setTitle("NO OP, CURRENTLY IN PROGRESS"));
}
public MessageBuilder removeNote(final Member performer, final int noteID, final NoteEntry entry) {
return moderation("moderation.note.remove.author")
.apply(builder -> messages.member(builder, "performer", performer))
.apply(builder -> noteEntry(builder, "note", noteID, entry))
.field("moderation.note.remove.field.performer", true)
.field("moderation.note.remove.field.case_id", true)
.field("moderation.note.remove.field.original_performer", true)
.field("moderation.note.remove.field.original_target", true)
.field("moderation.note.remove.field.date_time", true)
.field("moderation.note.remove.field.contents", true);
}
}

View File

@ -11,11 +11,16 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static sciwhiz12.janitor.Logging.JANITOR;
import static sciwhiz12.janitor.Logging.TRANSLATIONS;
public class Translations {
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<>() {};
@ -24,7 +29,7 @@ public class Translations {
private final Map<String, String> translations = new HashMap<>();
private final ObjectMapper jsonMapper = new ObjectMapper();
public Translations(JanitorBot bot, Path translationsFile) {
public TranslationMap(JanitorBot bot, Path translationsFile) {
this.bot = bot;
this.translationsFile = translationsFile;
loadTranslations();
@ -42,8 +47,7 @@ public class Translations {
translations.clear();
translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} translations from file {}", translations.size(), translationsFile);
}
catch (Exception e) {
} catch (Exception e) {
JANITOR.error(TRANSLATIONS, "Error while loading translations from file {}", translationsFile, e);
loadDefaultTranslations();
}
@ -59,8 +63,7 @@ public class Translations {
translations.clear();
translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} default english translations", translations.size());
}
catch (Exception e) {
} catch (Exception e) {
JANITOR.error(TRANSLATIONS, "Error while loading default english translations", e);
}
}
@ -69,7 +72,9 @@ public class Translations {
return Collections.unmodifiableMap(translations);
}
public String translate(String key, Object... args) {
return String.format(translations.getOrDefault(key, key), args);
public String translate(String text) {
final Matcher matcher = TRANSLATION_REGEX.matcher(text);
return matcher.replaceAll(
matchResult -> quoteReplacement(translations.getOrDefault(matchResult.group(1), matchResult.group(0))));
}
}

View File

@ -0,0 +1,9 @@
package sciwhiz12.janitor.msg.json;
import net.dv8tion.jda.api.EmbedBuilder;
import sciwhiz12.janitor.msg.substitution.ISubstitutor;
import sciwhiz12.janitor.msg.TranslationMap;
public interface IMessage {
EmbedBuilder create(TranslationMap translations, ISubstitutor substitutions);
}

View File

@ -0,0 +1,61 @@
package sciwhiz12.janitor.msg.json;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.time.OffsetDateTime;
public class ListingMessage {
protected final String url;
protected final String title;
protected final String description;
protected final OffsetDateTime timestamp;
protected final int color;
protected final MessageEmbed.Thumbnail thumbnail;
protected final MessageEmbed.AuthorInfo author;
protected final MessageEmbed.Footer footer;
protected final MessageEmbed.ImageInfo image;
protected final Multimap<FieldPlacement, MessageEmbed.Field> fields;
@Deprecated
public ListingMessage() {
this(null, null, null, null, 0, null, null, null, null, null);
}
public ListingMessage(MessageEmbed embed) {
this(embed.getUrl(),
embed.getTitle(),
embed.getDescription(),
embed.getTimestamp(),
embed.getColorRaw(),
embed.getThumbnail(),
embed.getAuthor(),
embed.getFooter(),
embed.getImage(),
Multimaps.index(embed.getFields(), k -> FieldPlacement.BEFORE));
}
public ListingMessage(String url, String title, String description, OffsetDateTime timestamp, int color,
MessageEmbed.Thumbnail thumbnail, MessageEmbed.AuthorInfo author, MessageEmbed.Footer footer,
MessageEmbed.ImageInfo image, Multimap<FieldPlacement, MessageEmbed.Field> fields) {
this.url = url;
this.title = title;
this.description = description;
this.timestamp = timestamp;
this.color = color;
this.thumbnail = thumbnail;
this.author = author;
this.footer = footer;
this.image = image;
this.fields = fields;
}
public enum ListingType {
DESCRIPTION, FIELDS
}
public enum FieldPlacement {
BEFORE, AFTER;
}
}

View File

@ -0,0 +1,213 @@
package sciwhiz12.janitor.msg.json;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.primitives.Ints;
import joptsimple.internal.Strings;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import sciwhiz12.janitor.msg.substitution.ISubstitutor;
import sciwhiz12.janitor.msg.TranslationMap;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;
import javax.annotation.Nullable;
@JsonDeserialize(using = RegularMessageDeserializer.class)
public class RegularMessage implements IMessage {
@Nullable
protected final String title;
@Nullable
protected final String url;
@Nullable
protected final String description;
@Nullable
protected final String color;
@Nullable
protected final String authorName;
@Nullable
protected final String authorUrl;
@Nullable
protected final String authorIconUrl;
@Nullable
protected final String footerText;
@Nullable
protected final String footerIconUrl;
@Nullable
protected final String imageUrl;
@Nullable
protected final String thumbnailUrl;
protected final List<MessageEmbed.Field> fields;
public RegularMessage(
@Nullable String title,
@Nullable String url,
@Nullable String description,
@Nullable String color,
@Nullable String authorName,
@Nullable String authorUrl,
@Nullable String authorIconUrl,
@Nullable String footerText,
@Nullable String footerIconUrl,
@Nullable String imageUrl,
@Nullable String thumbnailUrl,
List<MessageEmbed.Field> fields
) {
this.title = title;
this.url = url;
this.description = description;
this.color = color;
this.authorName = authorName;
this.authorUrl = authorUrl;
this.authorIconUrl = authorIconUrl;
this.footerText = footerText;
this.footerIconUrl = footerIconUrl;
this.imageUrl = imageUrl;
this.thumbnailUrl = thumbnailUrl;
this.fields = new ArrayList<>(fields);
}
@Nullable
public String getTitle() {
return title;
}
@Nullable
public String getUrl() {
return url;
}
@Nullable
public String getDescription() {
return description;
}
@Nullable
public String getColor() {
return color;
}
@Nullable
public String getAuthorName() {
return authorName;
}
@Nullable
public String getAuthorUrl() {
return authorUrl;
}
@Nullable
public String getAuthorIconUrl() {
return authorIconUrl;
}
@Nullable
public String getFooterText() {
return footerText;
}
@Nullable
public String getFooterIconUrl() {
return footerIconUrl;
}
@Nullable
public String getImageUrl() {
return imageUrl;
}
@Nullable
public String getThumbnailUrl() {
return thumbnailUrl;
}
public List<MessageEmbed.Field> getFields() {
return Collections.unmodifiableList(fields);
}
@Override
public String toString() {
return new StringJoiner(", ", RegularMessage.class.getSimpleName() + "[", "]")
.add("title='" + title + "'")
.add("url='" + url + "'")
.add("description='" + description + "'")
.add("color='" + color + "'")
.add("authorName='" + authorName + "'")
.add("authorUrl='" + authorUrl + "'")
.add("authorIconUrl='" + authorIconUrl + "'")
.add("footerText='" + footerText + "'")
.add("footerIconUrl='" + footerIconUrl + "'")
.add("imageUrl='" + imageUrl + "'")
.add("thumbnailUrl='" + thumbnailUrl + "'")
.add("fields=" + fields)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegularMessage that = (RegularMessage) o;
return Objects.equals(title, that.title) &&
Objects.equals(url, that.url) &&
Objects.equals(description, that.description) &&
Objects.equals(color, that.color) &&
Objects.equals(authorName, that.authorName) &&
Objects.equals(authorUrl, that.authorUrl) &&
Objects.equals(authorIconUrl, that.authorIconUrl) &&
Objects.equals(footerText, that.footerText) &&
Objects.equals(footerIconUrl, that.footerIconUrl) &&
Objects.equals(imageUrl, that.imageUrl) &&
Objects.equals(thumbnailUrl, that.thumbnailUrl) &&
fields.equals(that.fields);
}
@Override
public int hashCode() {
return Objects
.hash(title, url, description, color, authorName, authorUrl, authorIconUrl, footerText, footerIconUrl, imageUrl,
thumbnailUrl, fields);
}
@Override
public EmbedBuilder create(TranslationMap translations, ISubstitutor substitutions) {
final Function<String, String> func = str -> str != null ? substitutions.substitute(translations.translate(str)) : null;
final EmbedBuilder builder = new EmbedBuilder();
builder.setTitle(func.apply(title), func.apply(url));
builder.setColor(parseColor(substitutions.substitute(color)));
builder.setAuthor(func.apply(authorName), func.apply(authorUrl), func.apply(authorIconUrl));
builder.setDescription(func.apply(description));
builder.setImage(func.apply(imageUrl));
builder.setThumbnail(func.apply(thumbnailUrl));
builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
builder.setFooter(func.apply(footerText), func.apply(footerIconUrl));
for (MessageEmbed.Field field : fields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
}
return builder;
}
private static int parseColor(String str) {
if (Strings.isNullOrEmpty(str)) return Role.DEFAULT_COLOR_RAW;
if (str.startsWith("0x")) {
// noinspection UnstableApiUsage
final Integer res = Ints.tryParse(str.substring(2), 16);
if (res != null) {
return res;
}
}
// noinspection UnstableApiUsage
final Integer res = Ints.tryParse(str, 10);
if (res != null) {
return res;
}
return Role.DEFAULT_COLOR_RAW;
}
}

View File

@ -0,0 +1,93 @@
package sciwhiz12.janitor.msg.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
public class RegularMessageDeserializer extends StdDeserializer<RegularMessage> {
public RegularMessageDeserializer() {
super(RegularMessage.class);
}
@Override
public RegularMessage deserialize(JsonParser p, DeserializationContext ctx)
throws IOException {
final JsonNode node = ctx.readTree(p);
String title = null;
String url = null;
String description = node.path("description").asText(null);
String color = node.path("color").asText(null);
String authorName = null;
String authorUrl = null;
String authorIconUrl = null;
String footerText = null;
String footerIconUrl = null;
String imageUrl = node.path("image").asText(null);
String thumbnailUrl = node.path("thumbnail").asText(null);
List<MessageEmbed.Field> fields = readFields(node);
// Title
if (node.path("title").isTextual()) {
title = node.path("title").asText();
} else if (node.path("title").path("text").isTextual()) {
title = node.path("title").path("text").asText();
url = node.path("title").path("url").asText(null);
}
// Author
if (node.path("author").isTextual()) {
authorName = node.path("author").asText();
} else if (node.path("author").path("name").isTextual()) {
authorName = node.path("author").path("name").asText();
authorUrl = node.path("author").path("url").asText(null);
authorIconUrl = node.path("author").path("icon_url").asText(null);
}
// Footer
if (node.path("footer").isTextual()) {
footerText = node.path("footer").asText();
} else if (node.path("footer").path("text").isTextual()) {
footerText = node.path("footer").path("text").asText();
footerIconUrl = node.path("footer").path("icon_url").asText(null);
}
return new RegularMessage(title, url, description, color, authorName, authorUrl,
authorIconUrl, footerText, footerIconUrl, imageUrl, thumbnailUrl, fields);
}
public static List<MessageEmbed.Field> readFields(JsonNode node) {
if (node.path("fields").isArray()) {
final ArrayList<MessageEmbed.Field> fields = new ArrayList<>();
for (int i = 0; i < node.path("fields").size(); i++) {
final MessageEmbed.Field field = readField(node.path("fields").path(i));
if (field != null) {
fields.add(field);
}
}
return fields;
}
return Collections.emptyList();
}
@Nullable
public static MessageEmbed.Field readField(JsonNode fieldNode) {
if (fieldNode.path("name").isTextual() && fieldNode.path("value").isTextual()) {
return new MessageEmbed.Field(
fieldNode.path("name").asText(),
fieldNode.path("value").asText(),
fieldNode.path("inline").asBoolean(false)
);
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package sciwhiz12.janitor.msg.substitution;
import java.util.Map;
import java.util.function.Supplier;
public class CustomSubstitutions implements ISubstitutor {
private final Map<String, Supplier<String>> map;
public CustomSubstitutions(Map<String, Supplier<String>> map) {
this.map = map;
}
@Override
public String substitute(String text) {
return SubstitutionMap.substitute(text, map);
}
public Map<String, Supplier<String>> getMap() {
return map;
}
}

View File

@ -0,0 +1,5 @@
package sciwhiz12.janitor.msg.substitution;
public interface ISubstitutor {
String substitute(String text);
}

View File

@ -1,9 +1,11 @@
package sciwhiz12.janitor.msg;
package sciwhiz12.janitor.msg.substitution;
import org.apache.commons.collections4.TransformerUtils;
import org.apache.commons.collections4.map.DefaultedMap;
import sciwhiz12.janitor.JanitorBot;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@ -12,8 +14,9 @@ import java.util.regex.Pattern;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT;
public class Substitutions {
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);
@ -22,7 +25,8 @@ public class Substitutions {
return matcher.replaceAll(matchResult -> {
final Matcher nullMatcher = NULL_ARGUMENT_REGEX.matcher(matchResult.group(1));
if (nullMatcher.matches()) {
final String str = arguments.get(nullMatcher.group(1)).get();
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());
@ -34,8 +38,13 @@ public class Substitutions {
private final JanitorBot bot;
private final Map<String, Supplier<String>> defaultSubstitutions = new HashMap<>();
public Substitutions(JanitorBot bot) {
public SubstitutionMap(JanitorBot bot) {
this.bot = bot;
defaultSubstitutions.put("time.now", () -> OffsetDateTime.now(ZoneOffset.UTC).format(DATE_TIME_FORMAT));
defaultSubstitutions.put("moderation.color", () -> "0xF1BD25");
defaultSubstitutions.put("moderation.icon_url",
() -> "https://cdn.discordapp.com/attachments/738478941760782526/760463743330549760/gavel.png");
defaultSubstitutions.put("general.error.color", () -> "0xF73132");
}
public JanitorBot getBot() {
@ -43,17 +52,19 @@ public class Substitutions {
}
public String substitute(String text) {
return Substitutions.substitute(text, defaultSubstitutions);
return SubstitutionMap.substitute(text, defaultSubstitutions);
}
public String with(String text, Map<String, Supplier<String>> substitutions) {
return Substitutions.substitute(
text,
DefaultedMap.defaultedMap(substitutions, TransformerUtils.mapTransformer(defaultSubstitutions))
);
return SubstitutionMap.substitute(text, createDefaultedMap(substitutions));
}
public CustomSubstitutions with(Map<String, Supplier<String>> customSubstitutions) {
return new CustomSubstitutions(createDefaultedMap(customSubstitutions));
}
public Map<String, Supplier<String>> createDefaultedMap(Map<String, Supplier<String>> custom) {
return DefaultedMap.defaultedMap(custom, TransformerUtils.mapTransformer(defaultSubstitutions));
}
}

View File

@ -1,182 +1,118 @@
{
"general.guild_only_command.title": "Guild only command!",
"general.guild_only_command.description": "This command can only be run in a guild channel.",
"general.ambiguous_member.title": "Ambiguous member argument!",
"general.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.insufficient_permissions.title": "I have insufficient permissions!",
"general.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.insufficient_permissions.field.permissions.name": "Required permissions",
"general.insufficient_permissions.field.permissions.value": "${required_permissions}",
"general.cannot_interact.title": "Member is higher than me!",
"general.cannot_interact.description": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.",
"general.cannot_interact.field.target.name": "Target",
"general.cannot_interact.field.target.value": "${target.mention}",
"general.cannot_action_self.title": "Cannot act against myself!",
"general.cannot_action_self.description": "Cannot perform action against myself, as that would be counter-intuitive.",
"general.cannot_action_performer.title": "Performer cannot act against self!",
"general.cannot_action_performer.description": "You cannot perform this action against yourself.",
"general.cannot_action_performer.field.performer.name": "Performer/Target",
"general.cannot_action_performer.field.performer.value": "${performer.mention}",
"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.name": "Performer",
"moderation.insufficient_permissions.field.performer.value": "${performer.mention}",
"moderation.insufficient_permissions.field.permissions.name": "Required permissions",
"moderation.insufficient_permissions.field.permissions.value": "${required_permissions}",
"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.name": "Performer",
"moderation.cannot_interact.field.performer.value": "${performer.mention}",
"moderation.cannot_interact.field.target.name": "Target",
"moderation.cannot_interact.field.target.value": "${target.mention}",
"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.name": "Performer",
"moderation.kick.info.field.performer.value": "${performer.mention}",
"moderation.kick.info.field.target.name": "Target",
"moderation.kick.info.field.target.value": "${target.mention}",
"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.info.field.private_message.sent.name": "Sent DM",
"moderation.kick.info.field.private_message.sent.value": "✅",
"moderation.kick.info.field.private_message.unsent.name": "Sent DM",
"moderation.kick.info.field.private_message.unsent.value": "❌",
"moderation.kick.dm.author": "${performer.guild.name}",
"moderation.kick.dm.title": "You were kicked from this server.",
"moderation.kick.dm.field.performer.name": "Moderator",
"moderation.kick.dm.field.performer.value": "${performer.mention}",
"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.name": "Performer",
"moderation.ban.info.field.performer.value": "${performer.mention}",
"moderation.ban.info.field.target.name": "Target",
"moderation.ban.info.field.target.value": "${target.mention}",
"moderation.ban.info.field.reason.name": "Reason",
"moderation.ban.info.field.reason.value": "${nullcheck;reason;_No reason specified._}",
"moderation.ban.info.field.private_message.sent.name": "Sent DM",
"moderation.ban.info.field.private_message.sent.value": "✅",
"moderation.ban.info.field.private_message.unsent.name": "Sent DM",
"moderation.ban.info.field.private_message.unsent.value": "❌",
"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.dm.author": "${performer.guild.name}",
"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.name": "Moderator",
"moderation.ban.dm.field.performer.value": "${performer.mention}",
"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.name": "Performer",
"moderation.unban.info.field.performer.value": "${performer.mention}",
"moderation.unban.info.field.target.name": "Target",
"moderation.unban.info.field.target.value": "${target.mention}",
"moderation.unban.info.field.performer": "Performer",
"moderation.unban.info.field.target": "Target",
"moderation.warn.info.author": "Warned user.",
"moderation.warn.info.field.performer.name": "Performer",
"moderation.warn.info.field.performer.value": "${warning_entry.performer.mention}",
"moderation.warn.info.field.target.name": "Target",
"moderation.warn.info.field.target.value": "${warning_entry.target.mention}",
"moderation.warn.info.field.case_id.name": "Case ID",
"moderation.warn.info.field.case_id.value": "${warning_entry.case_id}",
"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.name": "Sent DM",
"moderation.warn.info.field.private_message.sent.value": "✅",
"moderation.warn.info.field.private_message.unsent.name": "Sent DM",
"moderation.warn.info.field.private_message.unsent.value": "❌",
"moderation.warn.info.field.date_time.name": "Date & Time",
"moderation.warn.info.field.date_time.value": "${warning_entry.date_time}",
"moderation.warn.dm.author": "${performer.guild.name}",
"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.name": "Moderator",
"moderation.warn.dm.field.performer.value": "${performer.mention}",
"moderation.warn.dm.field.date_time.name": "Date & Time",
"moderation.warn.dm.field.date_time.value": "${date_time}",
"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;reason;_No reason specified._}",
"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 #%1$s**: Warned %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Reason:_ %5$s",
"moderation.warnlist.entry.no_reason": "_no reason specified_",
"moderation.unwarn.author": "Removed warning from user.",
"moderation.unwarn.field.performer.name": "Performer",
"moderation.unwarn.field.performer.value": "${performer.mention}",
"moderation.unwarn.field.original_target.name": "Original Target",
"moderation.unwarn.field.original_target.value": "${warning_entry.target.mention}",
"moderation.unwarn.field.original_performer.name": "Original Performer",
"moderation.unwarn.field.original_performer.value": "${warning_entry.performer.mention}",
"moderation.unwarn.field.case_id.name": "Case ID",
"moderation.unwarn.field.case_id.value": "${warning_entry.case_id}",
"moderation.unwarn.field.date_time.name": "Date & Time",
"moderation.unwarn.field.date_time.value": "${warning_entry.date_time}",
"moderation.unwarn.field.reason.name": "Reason",
"moderation.unwarn.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.name": "Performer",
"moderation.unwarn.no_case_found.field.performer.value": "${performer.mention}",
"moderation.unwarn.no_case_found.field.case_id.name": "Case ID",
"moderation.unwarn.no_case_found.field.case_id.value": "${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.name": "Performer/Original Target",
"moderation.unwarn.cannot_unwarn_self.field.performer.value": "${performer.mention}",
"moderation.unwarn.cannot_unwarn_self.field.original_performer.name": "Original Performer",
"moderation.unwarn.cannot_unwarn_self.field.original_performer.value": "${warning_entry.performer.mention}",
"moderation.unwarn.cannot_unwarn_self.field.case_id.name": "Case ID",
"moderation.unwarn.cannot_unwarn_self.field.case_id.value": "${warning_entry.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.name": "Performer",
"moderation.warn.cannot_warn_mods.field.performer.value": "${performer.mention}",
"moderation.warn.cannot_warn_mods.field.target.name": "Target",
"moderation.warn.cannot_warn_mods.field.target.value": "${target.mention}",
"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.name": "Performer",
"moderation.warn.cannot_remove_higher_mod.field.performer.value": "${performer.mention}",
"moderation.warn.cannot_remove_higher_mod.field.original_performer.name": "Original Performer",
"moderation.warn.cannot_remove_higher_mod.field.original_performer.value": "${warning_entry.performer.mention}",
"moderation.warn.cannot_remove_higher_mod.field.case_id.name": "Case ID",
"moderation.warn.cannot_remove_higher_mod.field.case_id.value": "${warning_entry.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.name": "Performer",
"moderation.note.max_amount_of_notes.field.performer.value": "${performer.mention}",
"moderation.note.max_amount_of_notes.field.target.name": "Target",
"moderation.note.max_amount_of_notes.field.target.value": "${target.mention}",
"moderation.note.max_amount_of_notes.field.amount.name": "(Max.) Amount",
"moderation.note.max_amount_of_notes.field.amount.value": "${notes_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.name": "Performer",
"moderation.note.no_note_found.field.performer.value": "${performer.mention}",
"moderation.note.no_note_found.field.note_id.name": "Note ID",
"moderation.note.no_note_found.field.note_id.value": "${note_id}",
"moderation.note.list.author": "Listing of Notes",
"moderation.note.list.empty": "**_No recorded notes matching your query._**",
"moderation.note.list.entry": "**#%1$s**: for %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Text:_ %5$s",
"moderation.note.add.author": "Recorded note for user.",
"moderation.note.add.field.performer.name": "Performer",
"moderation.note.add.field.performer.value": "${note.performer.mention}",
"moderation.note.add.field.target.name": "Target",
"moderation.note.add.field.target.value": "${note.target.mention}",
"moderation.note.add.field.note_id.name": "Note ID",
"moderation.note.add.field.note_id.value": "${note.note_id}",
"moderation.note.add.field.date_time.name": "Date & Time",
"moderation.note.add.field.date_time.value": "${note.date_time}",
"moderation.note.add.field.contents.name": "Text",
"moderation.note.add.field.contents.value": "${note.contents}",
"moderation.note.remove.author": "Removed note.",
"moderation.note.remove.field.performer.name": "Performer",
"moderation.note.remove.field.performer.value": "${performer.mention}",
"moderation.note.remove.field.original_performer.name": "Original Performer",
"moderation.note.remove.field.original_performer.value": "${note.performer.mention}",
"moderation.note.remove.field.original_target.name": "Original Target",
"moderation.note.remove.field.original_target.value": "${note.target.mention}",
"moderation.note.remove.field.note_id.name": "Note ID",
"moderation.note.remove.field.note_id.value": "${note.note_id}",
"moderation.note.remove.field.date_time.name": "Date & Time",
"moderation.note.remove.field.date_time.value": "${note.date_time}",
"moderation.note.remove.field.contents.name": "Text",
"moderation.note.remove.field.contents.value": "${note.contents}"
"moderation.note.list.entry": "**#%1$s**: for %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Text:_ %5$s"
}

View File

@ -0,0 +1,5 @@
{
"color": "${general.error.color}",
"title": "<general.error.ambiguous_member.title>",
"description": "<general.error.ambiguous_member.description>"
}

View File

@ -0,0 +1,12 @@
{
"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
}
]
}

View File

@ -0,0 +1,5 @@
{
"color": "${general.error.color}",
"title": "<general.error.cannot_action_self.title>",
"description": "<general.error.cannot_action_self.description>"
}

View File

@ -0,0 +1,12 @@
{
"color": "${general.error.color}",
"title": "<general.error.cannot_interact.title>",
"description": "<general.error.cannot_interact.description>",
"fields": [
{
"name": "<general.error.cannot_interact.field.target>",
"value": "${target.mention}",
"inline": true
}
]
}

View File

@ -0,0 +1,5 @@
{
"color": "${general.error.color}",
"title": "<general.error.guild_only_command.title>",
"description": "<general.error.guild_only_command.description>"
}

View File

@ -0,0 +1,12 @@
{
"color": "${general.error.color}",
"title": "<general.error.insufficient_permissions.title>",
"description": "<general.error.insufficient_permissions.description>",
"fields": [
{
"name": "<general.error.insufficient_permissions.field.permissions>",
"value": "${required_permissions}",
"inline": true
}
]
}

View File

@ -0,0 +1,26 @@
[
"general/error/ambiguous_member",
"general/error/guild_only_command",
"general/error/insufficient_permissions",
"general/error/cannot_interact",
"general/error/cannot_action_self",
"general/error/cannot_action_performer",
"moderation/error/cannot_interact",
"moderation/error/insufficient_permissions",
"moderation/kick/info",
"moderation/kick/dm",
"moderation/ban/info",
"moderation/ban/dm",
"moderation/unban/info",
"moderation/warn/info",
"moderation/warn/dm",
"moderation/unwarn/info",
"moderation/error/unwarn/no_case_found",
"moderation/error/unwarn/cannot_unwarn_self",
"moderation/error/unwarn/cannot_remove_higher_mod",
"moderation/error/warn/cannot_warn_mods",
"moderation/error/note/max_amount_of_notes",
"moderation/error/note/no_note_found",
"moderation/note/add",
"moderation/note/remove"
]

View File

@ -0,0 +1,20 @@
{
"color": "${moderation.color}",
"author": {
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.ban.dm.title>",
"fields": [
{
"name": "<moderation.ban.dm.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.ban.dm.field.reason.name>",
"value": "<moderation.ban.dm.field.reason.value>",
"inline": true
}
]
}

View File

@ -0,0 +1,34 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.ban.info.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.ban.info.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.ban.info.field.target>",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.ban.info.field.private_message>",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.ban.info.field.delete_duration.name>",
"value": "<moderation.ban.info.field.delete_duration.value>",
"inline": true
},
{
"name": "<moderation.ban.info.field.reason.name>",
"value": "<moderation.ban.info.field.reason.value>",
"inline": false
}
]
}

View File

@ -0,0 +1,17 @@
{
"color": "${general.error.color}",
"title": "<moderation.cannot_interact.title>",
"description": "<moderation.cannot_interact.description>",
"fields": [
{
"name": "<moderation.cannot_interact.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.cannot_interact.field.target>",
"value": "${target.mention}",
"inline": true
}
]
}

View File

@ -0,0 +1,17 @@
{
"color": "${general.error.color}",
"title": "<moderation.insufficient_permissions.title>",
"description": "<moderation.insufficient_permissions.description>",
"fields": [
{
"name": "<moderation.insufficient_permissions.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.insufficient_permissions.field.permissions>",
"value": "${required_permissions}",
"inline": true
}
]
}

View File

@ -0,0 +1,22 @@
{
"color": "${general.error.color}",
"title": "<moderation.note.max_amount_of_notes.title>",
"description": "<moderation.note.max_amount_of_notes.description>",
"fields": [
{
"name": "<moderation.note.max_amount_of_notes.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.note.max_amount_of_notes.field.target>",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.note.max_amount_of_notes.field.amount>",
"value": "${notes_amount}",
"inline": true
}
]
}

View File

@ -0,0 +1,17 @@
{
"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
}
]
}

View File

@ -0,0 +1,22 @@
{
"color": "${general.error.color}",
"title": "<moderation.warn.cannot_remove_higher_mod.title>",
"description": "<moderation.warn.cannot_remove_higher_mod.description>",
"fields": [
{
"name": "<moderation.warn.cannot_remove_higher_mod.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.cannot_remove_higher_mod.field.original_performer>",
"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

@ -0,0 +1,22 @@
{
"color": "${general.error.color}",
"title": "<moderation.unwarn.cannot_unwarn_self.title>",
"description": "<moderation.unwarn.cannot_unwarn_self.description>",
"fields": [
{
"name": "<moderation.unwarn.cannot_unwarn_self.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.cannot_unwarn_self.field.original_performer>",
"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

@ -0,0 +1,17 @@
{
"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
}
]
}

View File

@ -0,0 +1,17 @@
{
"color": "${general.error.color}",
"title": "<moderation.warn.cannot_warn_mods.title>",
"description": "<moderation.warn.cannot_warn_mods.description>",
"fields": [
{
"name": "<moderation.warn.cannot_warn_mods.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.cannot_warn_mods.field.target>",
"value": "${target.mention}",
"inline": true
}
]
}

View File

@ -0,0 +1,20 @@
{
"color": "${moderation.color}",
"author": {
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.kick.dm.title>",
"fields": [
{
"name": "<moderation.kick.dm.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.kick.dm.field.reason.name>",
"value": "<moderation.kick.dm.field.reason.value>",
"inline": true
}
]
}

View File

@ -0,0 +1,29 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.kick.info.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.kick.info.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.kick.info.field.target>",
"value": "${target.mention}",
"inline": true
},
{
"name": "<moderation.kick.info.field.private_message>",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.kick.info.field.reason.name>",
"value": "<moderation.kick.info.field.reason.value>",
"inline": false
}
]
}

View File

@ -0,0 +1,34 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.add.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.note.add.field.performer>",
"value": "${note_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.note.add.field.target>",
"value": "${note_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.note.add.field.note_id>",
"value": "${note_entry.note_id}",
"inline": true
},
{
"name": "<moderation.note.add.field.date_time>",
"value": "${note_entry.date_time}",
"inline": true
},
{
"name": "<moderation.note.add.field.contents>",
"value": "${note_entry.contents}",
"inline": false
}
]
}

View File

@ -0,0 +1,39 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.remove.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.note.remove.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.original_performer>",
"value": "${note_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.original_target>",
"value": "${note_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.note.remove.field.note_id>",
"value": "${note_entry.note_id}",
"inline": true
},
{
"name": "<moderation.note.remove.field.date_time>",
"value": "${note_entry.date_time}",
"inline": true
},
{
"name": "<moderation.note.remove.field.contents>",
"value": "${note_entry.contents}",
"inline": false
}
]
}

View File

@ -0,0 +1,19 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.unban.info.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.unban.info.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unban.info.field.target>",
"value": "${target.mention}",
"inline": true
}
]
}

View File

@ -0,0 +1,39 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.unwarn.info.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.unwarn.info.field.performer>",
"value": "${performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.original_target>",
"value": "${warning_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.original_performer>",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.case_id>",
"value": "${warning_entry.case_id}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.date_time>",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.unwarn.info.field.reason.name>",
"value": "<moderation.unwarn.info.field.reason.value>",
"inline": false
}
]
}

View File

@ -0,0 +1,25 @@
{
"color": "${moderation.color}",
"author": {
"name": "${performer.guild.name}",
"icon_url": "${performer.guild.icon_url}"
},
"title": "<moderation.warn.dm.title>",
"fields": [
{
"name": "<moderation.warn.dm.field.performer>",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.dm.field.date_time>",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.warn.dm.field.reason.name>",
"value": "<moderation.warn.dm.field.reason.value>",
"inline": false
}
]
}

View File

@ -0,0 +1,39 @@
{
"color": "${moderation.color}",
"author": {
"name": "<moderation.warn.info.author>",
"icon_url": "${moderation.icon_url}"
},
"fields": [
{
"name": "<moderation.warn.info.field.performer>",
"value": "${warning_entry.performer.mention}",
"inline": true
},
{
"name": "<moderation.warn.info.field.target>",
"value": "${warning_entry.target.mention}",
"inline": true
},
{
"name": "<moderation.warn.info.field.case_id>",
"value": "${warning_entry.case_id}",
"inline": true
},
{
"name": "<moderation.warn.info.field.private_message>",
"value": "${private_message}",
"inline": true
},
{
"name": "<moderation.warn.info.field.date_time>",
"value": "${warning_entry.date_time}",
"inline": true
},
{
"name": "<moderation.warn.info.field.reason.name>",
"value": "<moderation.warn.info.field.reason.value>",
"inline": false
}
]
}