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

Merge in better_messages, reformat code, remove .idea folder

This commit is contained in:
Arnold Alejo Nunag 2020-10-16 18:23:04 +08:00
commit 0855569ad3
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
84 changed files with 2721 additions and 924 deletions

3
.idea/.gitignore vendored
View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="SciWhiz12" />
</state>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="11" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -1,30 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="NotNullFieldNotInitialized" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
<option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
<option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
<option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
</inspection_tool>
<inspection_tool class="unused" enabled="false" level="WARNING" enabled_by_default="false">
<option name="LOCAL_VARIABLE" value="true" />
<option name="FIELD" value="true" />
<option name="METHOD" value="true" />
<option name="CLASS" value="true" />
<option name="PARAMETER" value="true" />
<option name="REPORT_PARAMETER_FOR_PUBLIC_METHODS" value="true" />
<option name="ADD_MAINS_TO_ENTRIES" value="true" />
<option name="ADD_APPLET_TO_ENTRIES" value="true" />
<option name="ADD_SERVLET_TO_ENTRIES" value="true" />
<option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
<option name="selected" value="true" />
<option name="MIXIN_ENTRY_POINT" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component>
</project>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -40,7 +40,9 @@ dependencies {
implementation group: 'com.electronwill.night-config', name: 'toml', version: nightconfig_version
implementation group: 'net.sf.jopt-simple', name: 'jopt-simple', version: jopt_version
implementation group: 'com.google.guava', name: 'guava', version: guava_version
implementation group: 'com.google.code.gson', name: 'gson', version: gson_version
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jackson_version
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jackson_version
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jackson_version
implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: log4j_bridge_version
implementation group: 'ch.qos.logback', name: 'logback-classic', version: logback_version
implementation group: 'com.mojang', name: 'brigadier', version: brigadier_version

View File

@ -5,7 +5,7 @@ jda_version=4.2.0_207
nightconfig_version=3.6.3
jopt_version=6.0-alpha-3
guava_version=29.0-jre
gson_version=2.8.6
jackson_version=2.11.2
log4j_bridge_version=2.13.3
logback_version=1.3.0-alpha5
brigadier_version=1.0.17

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

@ -1,5 +1,6 @@
package sciwhiz12.janitor;
import com.google.common.base.Preconditions;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
@ -12,7 +13,6 @@ import sciwhiz12.janitor.config.BotOptions;
import java.util.EnumSet;
import static com.google.common.base.Preconditions.checkArgument;
import static sciwhiz12.janitor.Logging.JANITOR;
public class BotStartup {
@ -21,7 +21,7 @@ public class BotStartup {
BotOptions options = new BotOptions(args);
BotConfig config = new BotConfig(options);
checkArgument(!config.getToken().isEmpty(), "Supply a client token through config or command line");
Preconditions.checkArgument(!config.getToken().isEmpty(), "Supply a client token through config or command line");
JANITOR.info("Building bot instance and connecting to Discord...");
@ -37,8 +37,7 @@ public class BotStartup {
}
})
.build();
}
catch (Exception ex) {
} catch (Exception ex) {
JANITOR.error("Error while building Discord connection", ex);
}
}

View File

@ -9,7 +9,9 @@ import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.msg.Messages;
import sciwhiz12.janitor.msg.Translations;
import sciwhiz12.janitor.msg.TranslationMap;
import sciwhiz12.janitor.msg.emote.ReactionManager;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.utils.Util;
@ -22,22 +24,27 @@ import static sciwhiz12.janitor.Logging.STATUS;
public class JanitorBot {
private final JDA discord;
private final BotConfig config;
private final Messages messages;
private BotConsole console;
private final BotConsole console;
private final GuildStorage storage;
private final GuildStorage.SavingThread storageSavingThread;
private CommandRegistry cmdRegistry;
private Translations translations;
private final CommandRegistry cmdRegistry;
private final TranslationMap translations;
private final SubstitutionMap substitutions;
private final Messages messages;
private final ReactionManager reactions;
public JanitorBot(JDA discord, BotConfig config) {
this.config = config;
this.discord = discord;
this.console = new BotConsole(this, System.in);
this.storage = new GuildStorage(this, Path.of(config.STORAGE_PATH.get()));
this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix());
this.discord = discord;
this.translations = new Translations(this, config.getTranslationsFile());
this.messages = new Messages(this);
discord.addEventListener(cmdRegistry);
this.translations = new TranslationMap(this, config.getTranslationsFile());
this.substitutions = new SubstitutionMap(this);
this.messages = new Messages(this, config.getTranslationsFile());
this.reactions = new ReactionManager(this);
// TODO: find which of these can be loaded in parallel before the bot JDA is ready
discord.addEventListener(cmdRegistry, reactions);
discord.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(" n' sweeping n' testing!"));
discord.getGuilds().forEach(Guild::loadMembers);
JANITOR.info("Ready!");
@ -65,7 +72,9 @@ public class JanitorBot {
return this.config;
}
public Messages getMessages() { return this.messages; }
public Messages getMessages() {
return messages;
}
public GuildStorage getStorage() { return this.storage; }
@ -73,10 +82,14 @@ public class JanitorBot {
return this.cmdRegistry;
}
public Translations getTranslations() {
public TranslationMap getTranslations() {
return this.translations;
}
public ReactionManager getReactionManager() {
return this.reactions;
}
public void shutdown() {
JANITOR.info(STATUS, "Shutting down!");
getConfig().getOwnerID()
@ -102,4 +115,8 @@ public class JanitorBot {
storage.save();
console.stop();
}
public SubstitutionMap getSubstitutions() {
return substitutions;
}
}

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

@ -84,8 +84,7 @@ public class CommandRegistry implements EventListener {
}
JANITOR.debug(COMMANDS, "Executing command.");
dispatcher.execute(parseResults);
}
catch (CommandSyntaxException ex) {
} catch (CommandSyntaxException ex) {
JANITOR.error(COMMANDS, "Error while parsing message and executing command", ex);
}
}

View File

@ -20,8 +20,10 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class GuildMemberArgument implements ArgumentType<GuildMemberArgument.IMemberProvider> {
public static final SimpleCommandExceptionType UNKNOWN_MEMBER_IDENTIFIER = new SimpleCommandExceptionType(new LiteralMessage("Unknown user identifier"));
public static final SimpleCommandExceptionType MULTIPLE_MEMBERS = new SimpleCommandExceptionType(new LiteralMessage("Too many users, when only one is needed"));
public static final SimpleCommandExceptionType UNKNOWN_MEMBER_IDENTIFIER = new SimpleCommandExceptionType(
new LiteralMessage("Unknown user identifier"));
public static final SimpleCommandExceptionType MULTIPLE_MEMBERS = new SimpleCommandExceptionType(
new LiteralMessage("Too many users, when only one is needed"));
public static final Pattern USER_IDENTIFIER_PATTERN = Pattern.compile("<@!?([0-9]+)>");
@ -105,7 +107,8 @@ public class GuildMemberArgument implements ArgumentType<GuildMemberArgument.IMe
public List<Member> fromGuild(Guild guild) throws CommandSyntaxException {
final String nameLowercase = name.toLowerCase(Locale.ROOT);
final List<Member> members = guild.getMembers().stream()
.filter(member -> member.getUser().getAsTag().replaceAll("\\s", "").toLowerCase(Locale.ROOT).startsWith(nameLowercase))
.filter(member -> member.getUser().getAsTag().replaceAll("\\s", "").toLowerCase(Locale.ROOT)
.startsWith(nameLowercase))
.collect(Collectors.toList());
if (!multiple && members.size() > 1) {
throw MULTIPLE_MEMBERS.create();

View File

@ -24,8 +24,7 @@ public class HelloCommand extends BaseCommand {
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("greet")
.then(
argument("member", GuildMemberArgument.member())
.then(argument("member", GuildMemberArgument.member())
.executes(this::run)
);
}
@ -35,10 +34,21 @@ public class HelloCommand extends BaseCommand {
final List<Member> memberList = getMembers("member", ctx).fromGuild(ctx.getSource().getGuild());
if (memberList.size() == 1) {
final Member member = memberList.get(0);
ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!")
.queue(
success -> JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())),
err -> JANITOR.error("Error while sending greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor()))
ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!").queue(
success -> {
JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()),
Util.toString(ctx.getSource().getAuthor()));
getBot().getReactionManager().newMessage(success)
.add("\u274C", (msg, event) -> success.delete()
.flatMap(v -> event.getChannel()
.deleteMessageById(ctx.getSource().getMessageIdLong()))
.queue()
)
.owner(ctx.getSource().getAuthor().getIdLong())
.create();
},
err -> JANITOR.error("Error while sending greeting message to {}, on cmd of {}",
Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor()))
);
}
}

View File

@ -26,7 +26,8 @@ public class OKCommand extends BaseCommand {
.addReaction("\uD83D\uDC4C")
.queue(
success -> JANITOR.debug("Reacted :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor())),
err -> JANITOR.error("Error while reacting :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor()))
err -> JANITOR
.error("Error while reacting :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor()))
);
return 1;
}

View File

@ -8,14 +8,15 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
@ -60,43 +61,77 @@ public class BanCommand extends BaseCommand {
);
}
public int run(CommandContext<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()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<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))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION))
messages().GENERAL.insufficientPermissions(channel, BAN_PERMISSION).queue();
else if (!guild.getSelfMember().canInteract(target))
messages().GENERAL.cannotInteract(channel, target).queue();
else if (!performer.hasPermission(BAN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, BAN_PERMISSION).queue();
else if (!performer.canInteract(target))
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", BAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().canInteract(target)) {
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(BAN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", BAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
target.getUser().openPrivateChannel()
.flatMap(dm -> messages().MODERATION.bannedDM(dm, performer, reason))
.flatMap(dm -> messages().getRegularMessage("moderation/ban/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("reason", () -> reason)
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> ModerationHelper.banUser(target.getGuild(), performer, target, days, reason)
.flatMap(
v -> messages().MODERATION.banUser(channel, performer, target, reason, days, res.isSuccess())))
.flatMap(res ->
ModerationHelper.banUser(target.getGuild(), performer, target, days, reason)
.flatMap(v -> messages().getRegularMessage("moderation/ban/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("delete_duration", () -> String.valueOf(days))
.with("reason", () -> reason)
.send(getBot(), channel)
)
)
.queue();
}
return 1;
}
}

View File

@ -8,15 +8,16 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.CommandHelper;
import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
@ -50,36 +51,72 @@ public class KickCommand extends BaseCommand {
private int runWithReason(CommandContext<MessageReceivedEvent> ctx, @Nullable String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<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))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION))
messages().GENERAL.insufficientPermissions(channel, KICK_PERMISSION).queue();
else if (!guild.getSelfMember().canInteract(target))
messages().GENERAL.cannotInteract(channel, target).queue();
else if (!performer.hasPermission(KICK_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, KICK_PERMISSION).queue();
else if (!performer.canInteract(target))
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", KICK_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!guild.getSelfMember().canInteract(target)) {
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(KICK_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", KICK_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
target.getUser().openPrivateChannel()
.flatMap(dm -> messages().MODERATION.kickedDM(dm, performer, target, reason))
.flatMap(dm -> messages().getRegularMessage("moderation/kick/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("reason", () -> reason)
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> ModerationHelper.kickUser(target.getGuild(), performer, target, reason)
.flatMap(
v -> messages().MODERATION.kickUser(channel, performer, target, reason, res.isSuccess())))
.flatMap(v -> messages().getRegularMessage("moderation/kick/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("reason", () -> reason)
.send(getBot(), channel)
)
)
.queue();
}
return 1;
}
}

View File

@ -1,6 +1,6 @@
package sciwhiz12.janitor.commands.moderation;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -9,19 +9,21 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.notes.NoteStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
@ -32,6 +34,7 @@ import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member;
import static sciwhiz12.janitor.commands.moderation.NoteCommand.ModeratorFilter.*;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
import static sciwhiz12.janitor.msg.MessageHelper.*;
public class NoteCommand extends BaseCommand {
public static EnumSet<Permission> NOTE_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
@ -90,32 +93,56 @@ 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()) {
messages().GENERAL.guildOnlyCommand(ctx.getSource().getChannel());
messages().getRegularMessage("general/error/guild_only_command")
.apply(user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final Guild guild = performer.getGuild();
final MessageChannel channel = ctx.getSource().getChannel();
final List<Member> members = getMembers("target", ctx).fromGuild(guild);
if (members.size() < 1) return 1;
final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else {
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get();
if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) {
messages().MODERATION.ERRORS.maxAmountOfNotes(channel, performer, target, maxAmount).queue();
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("notes_amount", () -> String.valueOf(maxAmount))
.send(getBot(), channel).queue();
} else {
int noteID = storage.addNote(new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents));
messages().MODERATION.addNote(channel, performer, target, noteContents, dateTime, noteID).queue();
final NoteEntry entry = new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents);
int noteID = storage.addNote(entry);
messages().getRegularMessage("moderation/note/add")
.apply(MessageHelper.member("performer", performer))
.apply(noteEntry("note_entry", noteID, entry))
.send(getBot(), channel).queue();
}
}
return 1;
@ -127,9 +154,12 @@ public class NoteCommand extends BaseCommand {
private int listNotes(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, ModeratorFilter modFilter)
throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
final MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
@ -141,7 +171,10 @@ public class NoteCommand extends BaseCommand {
if (members.size() < 1) return 1;
final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue();
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
return 1;
}
predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong());
@ -156,44 +189,73 @@ public class NoteCommand extends BaseCommand {
case PERFORMER: {
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == performer.getIdLong());
}
case NONE: {}
}
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else
messages().MODERATION.noteList(channel, NoteStorage.get(getBot().getStorage(), guild)
} else {
messages().<Map.Entry<Integer, NoteEntry>>getListingMessage("moderation/note/list")
.apply(MessageHelper.member("performer", performer))
.amountPerPage(8)
.setEntryApplier((entry, subs) -> subs
.with("note_entry.note_id", () -> String.valueOf(entry.getKey()))
.apply(user("note_entry.performer", entry.getValue().getPerformer()))
.apply(user("note_entry.target", entry.getValue().getTarget()))
.with("note_entry.date_time", () -> entry.getValue().getDateTime().format(DATE_TIME_FORMAT))
.with("note_entry.contents", entry.getValue()::getContents)
)
.build(channel, getBot(), ctx.getSource().getMessage(),
NoteStorage.get(getBot().getStorage(), guild)
.getNotes()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).queue();
.sorted(Comparator.<Map.Entry<Integer, NoteEntry>>comparingInt(Map.Entry::getKey).reversed())
.collect(ImmutableList.toImmutableList())
);
}
return 1;
}
private int removeNote(CommandContext<MessageReceivedEvent> ctx, int noteID) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(NOTE_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", NOTE_PERMISSION::toString)
.send(getBot(), channel).queue();
if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else {
} else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
@Nullable
final NoteEntry entry = storage.getNote(noteID);
if (entry == null)
messages().MODERATION.ERRORS.noNoteFound(channel, performer, noteID).queue();
else {
if (entry == null) {
messages().getRegularMessage("moderation/note/add")
.apply(MessageHelper.member("performer", performer))
.with("note_id", () -> String.valueOf(noteID))
.send(getBot(), channel).queue();
} else {
storage.removeNote(noteID);
messages().MODERATION.removeNote(channel, performer, noteID, entry).queue();
messages().getRegularMessage("moderation/note/remove")
.apply(MessageHelper.member("performer", performer))
.apply(noteEntry("note_entry", noteID, entry))
.send(getBot(), channel).queue();
}
}
return 1;

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()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
@ -62,10 +66,14 @@ public class UnbanCommand extends BaseCommand {
.startsWith(username))
.collect(Collectors.toList()))
.queue(bans -> {
if (bans.size() > 1)
messages().GENERAL.ambiguousMember(channel).queue();
else if (bans.size() == 1)
if (bans.size() > 1) {
messages().getRegularMessage("general/error/ambiguous_member")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
} else if (bans.size() == 1) {
tryUnban(channel, guild, performer, bans.get(0).getUser());
}
});
}
@ -77,7 +85,10 @@ public class UnbanCommand extends BaseCommand {
void realIdRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
@ -97,13 +108,26 @@ public class UnbanCommand extends BaseCommand {
}
void tryUnban(MessageChannel channel, Guild guild, Member performer, User target) {
if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION))
messages().GENERAL.insufficientPermissions(channel, UNBAN_PERMISSION).queue();
else if (!performer.hasPermission(UNBAN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, UNBAN_PERMISSION).queue();
else
if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) {
messages().getRegularMessage("general/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", UNBAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(UNBAN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", UNBAN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else {
ModerationHelper.unbanUser(guild, target)
.flatMap(v -> messages().MODERATION.unbanUser(channel, performer, target))
.flatMap(v -> messages().getRegularMessage("moderation/unban/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.user("target", target))
.send(getBot(), channel)
)
.queue();
}
}
}

View File

@ -8,15 +8,15 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.util.EnumSet;
import java.util.Objects;
import javax.annotation.Nullable;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
@ -45,34 +45,54 @@ public class UnwarnCommand extends BaseCommand {
void realRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
int caseID = IntegerArgumentType.getInteger(ctx, "caseId");
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else {
} else {
final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild);
@Nullable
final WarningEntry entry = storage.getWarning(caseID);
Member temp;
if (entry == null)
messages().MODERATION.ERRORS.noWarnWithID(channel, performer, caseID).queue();
else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get())
messages().MODERATION.ERRORS.cannotUnwarnSelf(channel, performer, caseID, entry).queue();
else if (config().WARNINGS_RESPECT_MOD_ROLES.get()
&& (temp = guild.getMember(entry.getPerformer())) != null
&& !performer.canInteract(temp))
messages().MODERATION.ERRORS.cannotRemoveHigherModerated(channel, performer, caseID, entry).queue();
else {
if (entry == null) {
messages().getRegularMessage("moderation/error/unwarn/no_case_found")
.apply(MessageHelper.member("performer", performer))
.with("case_id", () -> String.valueOf(caseID))
.send(getBot(), channel).queue();
} else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) {
messages().getRegularMessage("moderation/error/unwarn/cannot_unwarn_self")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
} else if (config().WARNINGS_RESPECT_MOD_ROLES.get()
&& (temp = guild.getMember(entry.getPerformer())) != null && !performer.canInteract(temp)) {
messages().getRegularMessage("moderation/error/unwarn/cannot_remove_higher_mod")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
} else {
storage.removeWarning(caseID);
messages().MODERATION.unwarn(channel, performer, caseID, entry).queue();
messages().getRegularMessage("moderation/unwarn/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseID, entry))
.send(getBot(), channel).queue();
}
}
}

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,45 +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()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<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))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else if (!performer.canInteract(target))
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue();
else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get())
messages().MODERATION.ERRORS.cannotWarnMods(channel, performer, target).queue();
else
if (guild.getSelfMember().equals(target)) {
messages().getRegularMessage("general/error/cannot_action_self")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (performer.equals(target)) {
messages().getRegularMessage("general/error/cannot_action_performer")
.apply(MessageHelper.member("performer", performer))
.send(getBot(), channel).queue();
} else if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
} else if (!performer.canInteract(target)) {
messages().getRegularMessage("moderation/error/cannot_interact")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) {
messages().getRegularMessage("moderation/error/warn/cannot_warn_mods")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
} else {
WarningEntry entry = new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason);
int caseId = WarningStorage.get(getBot().getStorage(), guild).addWarning(entry);
target.getUser().openPrivateChannel()
.flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime))
.flatMap(dm -> messages().getRegularMessage("moderation/warn/dm")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
.send(getBot(), dm)
)
.mapToResult()
.flatMap(res -> {
int caseId = WarningStorage.get(getBot().getStorage(), guild)
.addWarning(new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason));
return messages().MODERATION
.warnUser(channel, performer, target, reason, dateTime, caseId, res.isSuccess());
})
.flatMap(res -> messages().getRegularMessage("moderation/warn/info")
.apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
.with("private_message", () -> res.isSuccess() ? "" : "")
.send(getBot(), channel)
)
.queue();
}
return 1;
}
}

View File

@ -1,6 +1,6 @@
package sciwhiz12.janitor.commands.moderation;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -13,8 +13,9 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@ -25,6 +26,8 @@ import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.getMember
import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT;
import static sciwhiz12.janitor.msg.MessageHelper.user;
public class WarnListCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
@ -54,18 +57,15 @@ public class WarnListCommand extends BaseCommand {
.executes(ctx -> this.run(ctx, false, false));
}
public int run(CommandContext<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()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
messages().getRegularMessage("general/error/guild_only_command")
.apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
@ -73,31 +73,50 @@ public class WarnListCommand extends BaseCommand {
if (filterTarget) {
final List<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)) {
messages().GENERAL.cannotActionSelf(channel).queue();
return;
messages().getRegularMessage("general/error/cannot_interact")
.apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
return 1;
}
predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong());
}
if (filterModerator) {
final List<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());
}
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(WARN_PERMISSION)) {
messages().getRegularMessage("moderation/error/insufficient_permissions")
.apply(MessageHelper.member("performer", performer))
.with("required_permissions", WARN_PERMISSION::toString)
.send(getBot(), channel).queue();
if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else
messages().MODERATION.warnList(channel, WarningStorage.get(getBot().getStorage(), guild)
} else {
messages().<Map.Entry<Integer, WarningEntry>>getListingMessage("moderation/warn/list")
.apply(MessageHelper.member("performer", performer))
.amountPerPage(8)
.setEntryApplier((entry, subs) -> subs
.with("warning_entry.case_id", () -> String.valueOf(entry.getKey()))
.apply(user("warning_entry.performer", entry.getValue().getPerformer()))
.apply(user("warning_entry.warned", entry.getValue().getWarned()))
.with("warning_entry.date_time", () -> entry.getValue().getDateTime().format(DATE_TIME_FORMAT))
.with("warning_entry.reason", entry.getValue()::getReason)
)
.build(channel, getBot(), ctx.getSource().getMessage(),
WarningStorage.get(getBot().getStorage(), guild)
.getWarnings()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).queue();
.sorted(Comparator.<Map.Entry<Integer, WarningEntry>>comparingInt(Map.Entry::getKey).reversed())
.collect(ImmutableList.toImmutableList())
);
}
return 1;
}
}

View File

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

View File

@ -4,11 +4,11 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.file.FileWatcher;
import com.electronwill.nightconfig.toml.TomlFormat;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import javax.annotation.Nullable;
import static sciwhiz12.janitor.Logging.CONFIG;
import static sciwhiz12.janitor.Logging.JANITOR;
@ -23,6 +23,7 @@ public class BotConfig {
public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL;
public final CommentedConfigSpec.ConfigValue<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

@ -14,6 +14,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 +29,10 @@ public class BotOptions {
.accepts("translations", "The path to the translations file")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(FILE_EXISTING, READABLE));
this.messagesFolder = parser
.accepts("translations", "The path to the custom messages folder")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(DIRECTORY_EXISTING, READABLE));
this.token = parser
.accepts("token", "The Discord token for the bot user")
.withRequiredArg();
@ -49,6 +54,10 @@ public class BotOptions {
return translationsPath.valueOptional(options);
}
public Optional<Path> getMessagesFolder() {
return messagesFolder.valueOptional(options);
}
public Optional<String> getToken() {
return token.valueOptional(options);
}

View File

@ -134,8 +134,7 @@ public class CommentedConfigSpec extends UnmodifiableConfigWrapper<UnmodifiableC
try {
isCorrecting = true;
ret = correct(this.config, config, parentPath, Collections.unmodifiableList(parentPath), listener, false);
}
finally {
} finally {
isCorrecting = false;
}
return ret;
@ -420,8 +419,7 @@ public class CommentedConfigSpec extends UnmodifiableConfigWrapper<UnmodifiableC
try {
//noinspection SuspiciousMethodCalls
return acceptableValues.contains(converter.get(obj, defaultValue.getClass()));
}
catch (IllegalArgumentException | ClassCastException e) {
} catch (IllegalArgumentException | ClassCastException e) {
return false;
}
});

View File

@ -1,18 +1,19 @@
package sciwhiz12.janitor.moderation.notes;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.JanitorBot;
import java.lang.reflect.Type;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.Objects;
import java.util.function.Supplier;
public class NoteEntry {
private final User performer;
@ -59,32 +60,42 @@ public class NoteEntry {
return Objects.hash(getPerformer(), getTarget(), getDateTime(), getContents());
}
public static class Serializer implements JsonDeserializer<NoteEntry>, JsonSerializer<NoteEntry> {
private final JanitorBot bot;
public static class Serializer extends StdSerializer<NoteEntry> {
private static final long serialVersionUID = 1L;
public Serializer(JanitorBot bot) {
public Serializer() {
super(NoteEntry.class);
}
@Override
public void serialize(NoteEntry value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("performer", value.getPerformer().getIdLong());
gen.writeNumberField("target", value.getTarget().getIdLong());
gen.writeStringField("dateTime", value.getDateTime().toString());
gen.writeStringField("contents", value.getContents());
gen.writeEndObject();
}
}
public static class Deserializer extends StdDeserializer<NoteEntry> {
private static final long serialVersionUID = 1L;
private final Supplier<JanitorBot> bot;
public Deserializer(Supplier<JanitorBot> bot) {
super(NoteEntry.class);
this.bot = bot;
}
@Override
public NoteEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final JsonObject obj = json.getAsJsonObject();
final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete();
final User target = bot.getDiscord().retrieveUserById(obj.get("target").getAsLong()).complete();
final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString());
final String reason = obj.get("contents").getAsString();
return new NoteEntry(performer, target, dateTime, reason);
}
@Override
public JsonElement serialize(NoteEntry src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject obj = new JsonObject();
obj.addProperty("performer", src.getPerformer().getId());
obj.addProperty("target", src.getTarget().getId());
obj.addProperty("dateTime", src.getDateTime().toString());
obj.addProperty("contents", src.getContents());
return obj;
public NoteEntry deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
final JsonNode obj = ctx.readTree(p);
User performer = bot.get().getDiscord().retrieveUserById(obj.get("performer").asLong()).complete();
User target = bot.get().getDiscord().retrieveUserById(obj.get("target").asLong()).complete();
OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").asText());
String contents = obj.get("contents").asText();
return new NoteEntry(performer, target, dateTime, contents);
}
}
}

View File

@ -1,11 +1,11 @@
package sciwhiz12.janitor.moderation.notes;
import com.electronwill.nightconfig.core.utils.ObservedMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -14,28 +14,24 @@ import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class NoteStorage extends JsonStorage {
private static final Type NOTE_MAP_TYPE = new TypeToken<Map<Integer, NoteEntry>>() {}.getType();
private static final TypeReference<Map<Integer, NoteEntry>> NOTE_MAP_TYPE = new TypeReference<>() {};
public static final StorageKey<NoteStorage> KEY = new StorageKey<>("notes", NoteStorage.class);
public static NoteStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, KEY, () -> new NoteStorage(storage.getBot()));
}
private final Gson gson;
private final JanitorBot bot;
private int lastID = 1;
private final Map<Integer, NoteEntry> notes = new ObservedMap<>(new HashMap<>(), this::markDirty);
public NoteStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {
@ -53,8 +49,8 @@ public class NoteStorage extends JsonStorage {
return notes.get(noteID);
}
public NoteEntry removeNote(int noteID) {
return notes.remove(noteID);
public void removeNote(int noteID) {
notes.remove(noteID);
}
public int getAmountOfNotes(User target) {
@ -68,18 +64,27 @@ public class NoteStorage extends JsonStorage {
}
@Override
public JsonElement save() {
JsonObject obj = new JsonObject();
obj.addProperty("lastNoteID", lastID);
obj.add("notes", gson.toJsonTree(notes));
protected void initialize(ObjectMapper mapper) {
super.initialize(mapper);
mapper.registerModule(
new SimpleModule()
.addSerializer(NoteEntry.class, new NoteEntry.Serializer())
.addDeserializer(NoteEntry.class, new NoteEntry.Deserializer(this::getBot))
);
}
@Override
public JsonNode save(ObjectMapper mapper) {
final ObjectNode obj = mapper.createObjectNode();
obj.put("lastNoteID", lastID);
obj.set("notes", mapper.valueToTree(notes));
return obj;
}
@Override
public void load(JsonElement in) {
final JsonObject obj = in.getAsJsonObject();
lastID = obj.get("lastNoteID").getAsInt();
final Map<Integer, NoteEntry> loaded = gson.fromJson(obj.get("notes"), NOTE_MAP_TYPE);
public void load(JsonNode in, ObjectMapper mapper) throws IOException {
lastID = in.get("lastNoteID").asInt();
final Map<Integer, NoteEntry> loaded = mapper.readerFor(NOTE_MAP_TYPE).readValue(in.get("notes"));
notes.clear();
notes.putAll(loaded);
}

View File

@ -1,18 +1,19 @@
package sciwhiz12.janitor.moderation.warns;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.JanitorBot;
import java.lang.reflect.Type;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.Objects;
import java.util.function.Supplier;
import javax.annotation.Nullable;
public class WarningEntry {
@ -62,35 +63,42 @@ public class WarningEntry {
return Objects.hash(getPerformer(), getWarned(), getDateTime(), getReason());
}
public static class Serializer implements JsonDeserializer<WarningEntry>, JsonSerializer<WarningEntry> {
private final JanitorBot bot;
public static class Serializer extends StdSerializer<WarningEntry> {
private static final long serialVersionUID = 1L;
public Serializer(JanitorBot bot) {
public Serializer() {
super(WarningEntry.class);
}
@Override
public void serialize(WarningEntry value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("performer", value.getPerformer().getIdLong());
gen.writeNumberField("warned", value.getWarned().getIdLong());
gen.writeStringField("dateTime", value.getDateTime().toString());
gen.writeStringField("reason", value.getReason());
gen.writeEndObject();
}
}
public static class Deserializer extends StdDeserializer<WarningEntry> {
private static final long serialVersionUID = 1L;
private final Supplier<JanitorBot> bot;
public Deserializer(Supplier<JanitorBot> bot) {
super(WarningEntry.class);
this.bot = bot;
}
@Override
public WarningEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final JsonObject obj = json.getAsJsonObject();
final User warned = bot.getDiscord().retrieveUserById(obj.get("warned").getAsLong()).complete();
final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete();
final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString());
@Nullable
final String reason = obj.has("reason") ? obj.get("reason").getAsString() : null;
return new WarningEntry(warned, performer, dateTime, reason);
}
@Override
public JsonElement serialize(WarningEntry src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject obj = new JsonObject();
obj.addProperty("warned", src.getWarned().getId());
obj.addProperty("performer", src.getPerformer().getId());
obj.addProperty("dateTime", src.getDateTime().toString());
if (src.getReason() != null) {
obj.addProperty("reason", src.getReason());
}
return obj;
public WarningEntry deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
final JsonNode obj = ctx.readTree(p);
User performer = bot.get().getDiscord().retrieveUserById(obj.get("performer").asLong()).complete();
User warned = bot.get().getDiscord().retrieveUserById(obj.get("warned").asLong()).complete();
OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").asText());
String contents = obj.get("reason").asText();
return new WarningEntry(performer, warned, dateTime, contents);
}
}
}

View File

@ -1,11 +1,11 @@
package sciwhiz12.janitor.moderation.warns;
import com.electronwill.nightconfig.core.utils.ObservedMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import net.dv8tion.jda.api.entities.Guild;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.JanitorBot;
@ -13,28 +13,23 @@ import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class WarningStorage extends JsonStorage {
private static final Type WARNING_MAP_TYPE = new TypeToken<Map<Integer, WarningEntry>>() {}.getType();
private static final TypeReference<Map<Integer, WarningEntry>> WARNING_MAP_TYPE = new TypeReference<>() {};
public static final StorageKey<WarningStorage> KEY = new StorageKey<>("warnings", WarningStorage.class);
public static WarningStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, KEY, () -> new WarningStorage(storage.getBot()));
}
private final Gson gson;
private final JanitorBot bot;
private int lastID = 1;
private final Map<Integer, WarningEntry> warnings = new ObservedMap<>(new HashMap<>(), this::markDirty);
public WarningStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {
@ -52,8 +47,8 @@ public class WarningStorage extends JsonStorage {
return warnings.get(caseID);
}
public WarningEntry removeWarning(int caseID) {
return warnings.remove(caseID);
public void removeWarning(int caseID) {
warnings.remove(caseID);
}
public Map<Integer, WarningEntry> getWarnings() {
@ -61,18 +56,27 @@ public class WarningStorage extends JsonStorage {
}
@Override
public JsonElement save() {
JsonObject obj = new JsonObject();
obj.addProperty("lastCaseID", lastID);
obj.add("warnings", gson.toJsonTree(warnings));
protected void initialize(ObjectMapper mapper) {
super.initialize(mapper);
mapper.registerModule(
new SimpleModule()
.addSerializer(WarningEntry.class, new WarningEntry.Serializer())
.addDeserializer(WarningEntry.class, new WarningEntry.Deserializer(this::getBot))
);
}
@Override
public JsonNode save(ObjectMapper mapper) {
final ObjectNode obj = mapper.createObjectNode();
obj.put("lastCaseID", lastID);
obj.set("warnings", mapper.valueToTree(warnings));
return obj;
}
@Override
public void load(JsonElement in) {
final JsonObject obj = in.getAsJsonObject();
lastID = obj.get("lastCaseID").getAsInt();
final Map<Integer, WarningEntry> loaded = gson.fromJson(obj.get("warnings"), WARNING_MAP_TYPE);
public void load(JsonNode in, ObjectMapper mapper) {
lastID = in.get("lastCaseID").asInt();
final Map<Integer, WarningEntry> loaded = mapper.convertValue(in.get("warnings"), WARNING_MAP_TYPE);
warnings.clear();
warnings.putAll(loaded);
}

View File

@ -1,79 +0,0 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.requests.RestAction;
import java.util.EnumSet;
import java.util.stream.Collectors;
public final class General {
private final Messages messages;
General(Messages messages) {
this.messages = messages;
}
private String translate(String key, Object... args) {
return messages.translate(key, args);
}
public RestAction<Message> guildOnlyCommand(MessageChannel channel) {
return channel.sendMessage(
messages.failureEmbed(translate("general.guild_only_command.title"))
.setDescription(translate("general.guild_only_command.desc"))
.build()
);
}
public RestAction<Message> insufficientPermissions(MessageChannel channel, EnumSet<Permission> permissions) {
return channel.sendMessage(
messages.failureEmbed(translate("general.insufficient_permissions.title"))
.setDescription(translate("general.insufficient_permissions.desc"))
.addField(new MessageEmbed.Field(
translate("general.insufficient_permissions.field.permissions"),
permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")),
false))
.build()
);
}
public RestAction<Message> ambiguousMember(MessageChannel channel) {
return channel.sendMessage(
messages.failureEmbed(translate("general.ambiguous_member.title"))
.setDescription(translate("general.ambiguous_member.desc"))
.build()
);
}
public RestAction<Message> cannotInteract(MessageChannel channel, Member target) {
return channel.sendMessage(
messages.failureEmbed(translate("general.cannot_interact.title"))
.setDescription(translate("general.cannot_interact.desc"))
.addField(translate("general.cannot_interact.field.target"), target.getAsMention(), true)
.build()
);
}
public RestAction<Message> cannotActionSelf(MessageChannel channel) {
return channel.sendMessage(
messages.failureEmbed(translate("general.cannot_action_self.title"))
.setDescription(translate("general.cannot_action_self.desc"))
.build()
);
}
public RestAction<Message> cannotActionPerformer(MessageChannel channel, Member performer) {
return channel.sendMessage(
messages.failureEmbed(translate("general.cannot_action_performer.title"))
.setDescription(translate("general.cannot_action_performer.desc"))
.addField(translate("general.cannot_action_performer.field.performer"),
performer.getUser().getAsMention(),
true)
.build()
);
}
}

View File

@ -0,0 +1,161 @@
package sciwhiz12.janitor.msg;
import com.google.common.collect.ImmutableList;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.msg.json.ListingMessage;
import sciwhiz12.janitor.msg.substitution.CustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ListingMessageBuilder<T> implements IHasCustomSubstitutions<ListingMessageBuilder<T>> {
private final ListingMessage message;
private final Map<String, Supplier<String>> customSubstitutions;
private int amountPerPage = 10;
private BiConsumer<T, CustomSubstitutions> entryApplier = (entry, sub) -> {};
public ListingMessageBuilder(ListingMessage message, Map<String, Supplier<String>> customSubstitutions) {
this.message = message;
this.customSubstitutions = customSubstitutions;
}
public ListingMessageBuilder(ListingMessage message) {
this(message, new HashMap<>());
}
public ListingMessageBuilder<T> amountPerPage(int amountPerPage) {
this.amountPerPage = amountPerPage;
return this;
}
public ListingMessageBuilder<T> setEntryApplier(BiConsumer<T, CustomSubstitutions> entryApplier) {
this.entryApplier = entryApplier;
return this;
}
public ListingMessageBuilder<T> apply(Consumer<ListingMessageBuilder<T>> consumer) {
consumer.accept(this);
return this;
}
public ListingMessageBuilder<T> with(final String argument, final Supplier<String> value) {
this.customSubstitutions.put(argument, value);
return this;
}
public void build(MessageChannel channel, TranslationMap translations, SubstitutionMap globalSubstitutions,
Message triggerMessage, List<T> entries) {
final CustomSubstitutions customSubs = globalSubstitutions.with(customSubstitutions);
final ImmutableList<T> list = ImmutableList.copyOf(entries);
final PagedMessage pagedMessage = new PagedMessage(message, list, amountPerPage);
channel.sendMessage(pagedMessage.createMessage(translations, customSubs, entryApplier))
.queue(listMsg -> translations.getBot().getReactionManager().newMessage(listMsg)
.owner(triggerMessage.getAuthor().getIdLong())
.removeEmotes(true)
.add("\u2b05", (msg, event) -> { // PREVIOUS
if (pagedMessage.advancePage(PageDirection.PREVIOUS)) {
event.retrieveMessage()
.flatMap(eventMsg -> eventMsg.editMessage(
pagedMessage.createMessage(translations, customSubs, entryApplier))
)
.queue();
}
})
.add("\u274c", (msg, event) -> { // CLOSE
event.getChannel().deleteMessageById(event.getMessageIdLong())
.flatMap(v -> !triggerMessage.isFromGuild() ||
event.getGuild().getSelfMember()
.hasPermission(triggerMessage.getTextChannel(),
Permission.MESSAGE_MANAGE),
v -> triggerMessage.delete())
.queue();
})
.add("\u27a1", (msg, event) -> { // NEXT
if (pagedMessage.advancePage(PageDirection.NEXT)) {
event.retrieveMessage()
.flatMap(eventMsg -> eventMsg.editMessage(
pagedMessage.createMessage(translations, customSubs, entryApplier))
)
.queue();
}
})
.create()
);
}
public void build(MessageChannel channel, JanitorBot bot, Message triggerMessage, List<T> entries) {
build(channel, bot.getTranslations(), bot.getSubstitutions(), triggerMessage, entries);
}
class PagedMessage {
private final ListingMessage message;
private final ImmutableList<T> list;
private final int maxPages;
private final int amountPerPage;
private int currentPage = 0;
private int lastPage = -1;
private EmbedBuilder cachedMessage;
PagedMessage(ListingMessage message, ImmutableList<T> list, int amountPerPage) {
this.message = message;
this.list = list;
this.amountPerPage = amountPerPage;
this.maxPages = Math.floorDiv(list.size(), ListingMessageBuilder.this.amountPerPage);
}
public int getMaxPages() {
return maxPages;
}
public int getCurrentPage() {
return currentPage;
}
public boolean advancePage(PageDirection direction) {
if (direction == PageDirection.PREVIOUS && currentPage > 0) {
currentPage -= 1;
return true;
} else if (direction == PageDirection.NEXT && currentPage < maxPages) {
currentPage += 1;
return true;
}
return false;
}
public MessageEmbed createMessage(TranslationMap translations, CustomSubstitutions substitutions,
BiConsumer<T, CustomSubstitutions> applier) {
if (currentPage != lastPage) {
cachedMessage = message.create(
translations,
substitutions.with(new HashMap<>())
.with("page.max", () -> String.valueOf(maxPages + 1))
.with("page.current", () -> String.valueOf(currentPage + 1)),
list.stream()
.skip(currentPage * amountPerPage)
.limit(amountPerPage)
.collect(Collectors.toList()),
applier);
lastPage = currentPage;
}
return cachedMessage.build();
}
}
enum PageDirection {
PREVIOUS, NEXT
}
}

View File

@ -0,0 +1,111 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.function.Consumer;
import static java.time.temporal.ChronoField.*;
public class MessageHelper {
private MessageHelper() {}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> snowflake(String head, ISnowflake snowflake) {
return builder -> builder
.with(head + ".id", snowflake::getId)
.with(head + ".creation_datetime", () -> snowflake.getTimeCreated().format(DATE_TIME_FORMAT));
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> mentionable(String head, IMentionable mentionable) {
return builder -> builder
.apply(snowflake(head, mentionable))
.with(head + ".mention", mentionable::getAsMention);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> 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 <T extends IHasCustomSubstitutions<?>> Consumer<T> 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 <T extends IHasCustomSubstitutions<?>> Consumer<T> 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 <T extends IHasCustomSubstitutions<?>> Consumer<T> 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 <T extends IHasCustomSubstitutions<?>> Consumer<T> warningEntry(String head, int caseID, WarningEntry entry) {
return builder -> builder
.with(head + ".case_id", () -> String.valueOf(caseID))
.apply(user(head + ".performer", entry.getPerformer()))
.apply(user(head + ".target", entry.getWarned()))
.with(head + ".date_time", () -> entry.getDateTime().format(DATE_TIME_FORMAT))
.with(head + ".reason", entry::getReason);
}
public static <T extends IHasCustomSubstitutions<?>> Consumer<T> noteEntry(String head, int noteID, NoteEntry entry) {
return builder -> builder
.with(head + ".note_id", () -> String.valueOf(noteID))
.apply(user(head + ".performer", entry.getPerformer()))
.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,32 +1,161 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.EmbedBuilder;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.dv8tion.jda.api.entities.MessageEmbed;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.msg.json.ListingMessage;
import sciwhiz12.janitor.msg.json.RegularMessage;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.io.Resources.getResource;
import static sciwhiz12.janitor.Logging.JANITOR;
import static sciwhiz12.janitor.Logging.MESSAGES;
public class Messages {
public static final int FAILURE_COLOR = 0xF73132;
public static final String JSON_FILE_SUFFIX = ".json";
public static final String MESSAGES_FILENAME = "messages";
public static final String DEFAULT_MESSAGES_FOLDER = "messages/";
public static final TypeReference<List<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 Map<String, ListingMessage> listingMessages = new HashMap<>();
private final ObjectMapper jsonMapper = new ObjectMapper();
public Messages(JanitorBot bot) {
public Messages(JanitorBot bot, Path messagesFolder) {
this.bot = bot;
this.GENERAL = new General(this);
this.MODERATION = new Moderation(this);
this.messagesFolder = messagesFolder;
loadMessages();
}
public String translate(String key, Object... args) {
return bot.getTranslations().translate(key, args);
public JanitorBot getBot() {
return bot;
}
public EmbedBuilder failureEmbed(String title) {
return new EmbedBuilder()
.setTitle(title)
.setColor(FAILURE_COLOR)
.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
public void loadMessages() {
boolean success = false;
if (messagesFolder != null) {
JANITOR.debug(MESSAGES, "Loading messages from folder {}", messagesFolder);
success = loadMessages(
path -> Files.newBufferedReader(messagesFolder.resolve(path + JSON_FILE_SUFFIX))
);
} else {
JANITOR.info(MESSAGES, "No custom messages folder specified");
}
if (!success) {
JANITOR.info(MESSAGES, "Loading default messages");
//noinspection UnstableApiUsage
loadMessages(
file -> new InputStreamReader(getResource(DEFAULT_MESSAGES_FOLDER + file + JSON_FILE_SUFFIX).openStream())
);
}
}
boolean loadMessages(FileOpener files) {
try (Reader keyReader = files.open(MESSAGES_FILENAME)) {
List<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);
final String type = tree.path("type").asText("regular");
if ("regular".equals(type)) {
regularMessages.put(messageKey, jsonMapper.convertValue(tree, RegularMessage.class));
} else if ("listing".equals(type)) {
listingMessages.put(messageKey, jsonMapper.convertValue(tree, ListingMessage.class));
} else {
JANITOR.warn(MESSAGES, "Unknown message type {} for {}", tree.path("type").asText(), messageKey);
}
} catch (Exception e) {
JANITOR.error(MESSAGES, "Error while loading message {}", path, e);
}
}
JANITOR.info(MESSAGES, "Loaded {} messages", regularMessages.size());
return true;
} catch (Exception e) {
JANITOR.error(MESSAGES, "Error while loading messages", e);
return false;
}
}
public Map<String, RegularMessage> getRegularMessages() {
return Collections.unmodifiableMap(regularMessages);
}
public RegularMessageBuilder getRegularMessage(String messageKey) {
final RegularMessage msg = regularMessages.get(messageKey);
if (msg == null) {
JANITOR.warn(MESSAGES, "Attempted to get unknown regular message with key {}", messageKey);
return new RegularMessageBuilder(UNKNOWN_REGULAR_MESSAGE).with("key", () -> messageKey);
}
return new RegularMessageBuilder(msg);
}
public Map<String, ListingMessage> getListingMessages() {
return listingMessages;
}
public <T> ListingMessageBuilder<T> getListingMessage(String messageKey) {
final ListingMessage msg = listingMessages.get(messageKey);
if (msg == null) {
JANITOR.warn(MESSAGES, "Attempted to get unknown listing message with key {}", messageKey);
return new ListingMessageBuilder<T>(UNKNOWN_LISTING_MESSAGE).with("key", () -> messageKey);
}
return new ListingMessageBuilder<>(msg);
}
interface FileOpener {
Reader open(String filePath) throws IOException;
}
public static final RegularMessage UNKNOWN_REGULAR_MESSAGE = new RegularMessage(
"UNKNOWN MESSAGE!",
null,
"A regular message was tried to be looked up, but was not found. Please report this to your bot " +
"maintainer/administrator.",
String.valueOf(0xFF0000),
null,
null,
null,
null,
null,
null,
null,
Collections.singletonList(new MessageEmbed.Field("Message Key", "${key}", false))
);
public static final ListingMessage UNKNOWN_LISTING_MESSAGE = new ListingMessage(
"UNKNOWN MESSAGE!",
null,
"A listing message was tried to be looked up, but was not found. " +
"Please report this to your bot maintainer/administrator.",
String.valueOf(0xFF0000),
null,
null,
null,
null,
null,
null,
null,
null,
new ListingMessage.DescriptionEntry(null, ""),
Collections.singletonList(new MessageEmbed.Field("Message Key", "${key}", false)),
Collections.emptyList()
);
}

View File

@ -1,318 +0,0 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Map;
import java.util.stream.Collectors;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
public final class Moderation {
public static final int MODERATION_COLOR = 0xF1BD25;
public static final String GAVEL_ICON_URL = "https://cdn.discordapp.com/attachments/738478941760782526" +
"/760463743330549760/gavel.png";
private final Messages messages;
public final Errors ERRORS;
Moderation(Messages messages) {
this.messages = messages;
ERRORS = new Errors();
}
private String translate(String key, Object... args) {
return messages.translate(key, args);
}
public EmbedBuilder moderationEmbed() {
return new EmbedBuilder()
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
}
public EmbedBuilder moderationEmbed(String author) {
return moderationEmbed()
.setAuthor(author, null, GAVEL_ICON_URL);
}
public class Errors {
private Errors() {}
public MessageAction performerInsufficientPermissions(MessageChannel channel, Member performer,
EnumSet<Permission> permissions) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.insufficient_permissions.title"))
.setDescription(translate("moderation.insufficient_permissions.desc"))
.addField(
translate("moderation.insufficient_permissions.field.performer"),
performer.getAsMention(),
true)
.addField(new MessageEmbed.Field(
translate("moderation.insufficient_permissions.field.permissions"),
permissions.stream().map(Permission::getName).collect(Collectors.joining(", ")), true))
.build()
);
}
public MessageAction cannotModerate(MessageChannel channel, Member performer, Member target) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.cannot_interact.title"))
.setDescription(translate("moderation.cannot_interact.desc"))
.addField(translate("moderation.cannot_interact.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.cannot_interact.field.target"), target.getAsMention(), true)
.build()
);
}
public MessageAction cannotWarnMods(MessageChannel channel, Member performer, Member target) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.warn.cannot_warn_mods.title"))
.setDescription(translate("moderation.warn.cannot_warn_mods.desc"))
.addField(translate("moderation.warn.cannot_warn_mods.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.warn.cannot_warn_mods.field.target"), target.getAsMention(), true).build()
);
}
public MessageAction cannotRemoveHigherModerated(MessageChannel channel, Member performer, int caseID,
WarningEntry entry) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.unwarn.cannot_remove_higher_mod.title"))
.setDescription(translate("moderation.unwarn.cannot_remove_higher_mod.desc"))
.addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.performer"), performer.getAsMention(),
true)
.addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.original_performer"),
entry.getPerformer().getAsMention(), true)
.addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.case_id"), String.valueOf(caseID),
true)
.build()
);
}
public MessageAction maxAmountOfNotes(MessageChannel channel, Member performer, Member target, int amount) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.note.max_amount_of_notes.title"))
.setDescription(translate("moderation.note.max_amount_of_notes.desc"))
.addField(translate("moderation.note.max_amount_of_notes.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.note.max_amount_of_notes.field.target"), target.getAsMention(), true)
.addField(translate("moderation.note.max_amount_of_notes.field.amount"), String.valueOf(amount), true)
.build()
);
}
public MessageAction noNoteFound(MessageChannel channel, Member performer, int noteID) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.note.no_note_found.title"))
.setDescription(translate("moderation.note.no_note_found.desc"))
.addField(translate("moderation.note.no_note_found.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.note.no_note_found.field.note_id"), String.valueOf(noteID), true)
.build()
);
}
public MessageAction noWarnWithID(MessageChannel channel, Member performer, int caseID) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.unwarn.no_case_found.title"))
.setDescription(translate("moderation.unwarn.no_case_found.desc"))
.addField(translate("moderation.unwarn.no_case_found.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.unwarn.no_case_found.field.case_id"), String.valueOf(caseID), true).build()
);
}
public MessageAction cannotUnwarnSelf(MessageChannel channel, Member performer, int caseID, WarningEntry entry) {
return channel.sendMessage(
messages.failureEmbed(translate("moderation.unwarn.cannot_unwarn_self.title"))
.setDescription(translate("moderation.unwarn.cannot_unwarn_self.desc"))
.addField(translate("moderation.unwarn.cannot_unwarn_self.field.performer"), performer.getAsMention(),
true)
.addField(translate("moderation.unwarn.cannot_unwarn_self.field.original_performer"),
entry.getPerformer().getAsMention(), true)
.addField(translate("moderation.unwarn.cannot_unwarn_self.field.case_id"), String.valueOf(caseID), true)
.build()
);
}
}
public MessageAction kickUser(MessageChannel channel, Member performer, Member target, @Nullable String reason,
boolean sentDM) {
return channel.sendMessage(
moderationEmbed(translate("moderation.kick.info.author"))
.addField(translate("moderation.kick.info.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.kick.info.field.target"), target.getAsMention(), true)
.addField(translate("moderation.kick.info.field.sent_private_message"), sentDM ? "" : "", true)
.addField(reason != null ? translate("moderation.kick.info.field.reason") : null, reason, false)
.build()
);
}
public MessageAction kickedDM(MessageChannel channel, Member performer, Member target, @Nullable String reason) {
return channel.sendMessage(
moderationEmbed()
.setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl())
.setTitle(translate("moderation.kick.dm.title"))
.addField(translate("moderation.kick.dm.field.performer"), performer.getUser().getAsMention(), true)
.addField(reason != null ? translate("moderation.kick.dm.field.reason") : null, reason, false)
.build()
);
}
public MessageAction banUser(MessageChannel channel, Member performer, Member target, @Nullable String reason,
int deletionDays, boolean sentDM) {
return channel.sendMessage(
moderationEmbed(translate("moderation.ban.info.author"))
.addField(translate("moderation.ban.info.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.ban.info.field.target"), target.getAsMention(), true)
.addField(translate("moderation.ban.info.field.sent_private_message"), sentDM ? "" : "", true)
.addField(deletionDays != 0 ?
new MessageEmbed.Field(translate("moderation.ban.info.field.delete_duration"),
translate("moderation.ban.info.field.delete_duration.value", String.valueOf(deletionDays)), true)
: null)
.addField(reason != null ? translate("moderation.ban.info.field.reason") : null, reason, false)
.build()
);
}
public MessageAction bannedDM(MessageChannel channel, Member performer, @Nullable String reason) {
return channel.sendMessage(
moderationEmbed()
.setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl())
.setTitle(translate("moderation.ban.dm.title"))
.addField(translate("moderation.ban.dm.field.performer"), performer.getAsMention(), true)
.addField(reason != null ? translate("moderation.ban.dm.field.reason") : null, reason, false)
.build()
);
}
public MessageAction unbanUser(MessageChannel channel, Member performer, User target) {
return channel.sendMessage(
moderationEmbed(translate("moderation.unban.info.author"))
.addField(translate("moderation.unban.info.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.unban.info.field.target"), target.getAsMention(), true)
.build()
);
}
public MessageAction warnUser(MessageChannel channel, Member performer, Member target, String reason,
OffsetDateTime dateTime, int caseID, boolean sentDM) {
return channel.sendMessage(
moderationEmbed(translate("moderation.warn.info.author"))
.addField(translate("moderation.warn.info.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.warn.info.field.target"), target.getAsMention(), true)
.addField(translate("moderation.warn.info.field.sent_private_message"), sentDM ? "" : "", true)
.addField(translate("moderation.warn.info.field.case_id"), String.valueOf(caseID), true)
.addField(translate("moderation.warn.info.field.date_time"),
dateTime.format(RFC_1123_DATE_TIME), true)
.addField(translate("moderation.warn.info.field.reason"), reason, false).build()
);
}
public MessageAction warnDM(MessageChannel channel, Member performer, Member target, String reason,
OffsetDateTime dateTime) {
return channel.sendMessage(
moderationEmbed()
.setAuthor(performer.getGuild().getName(), null, performer.getGuild().getIconUrl())
.setTitle(translate("moderation.warn.dm.title"))
.addField(translate("moderation.warn.dm.field.performer"), performer.getUser().getAsMention(), true)
.addField(translate("moderation.warn.dm.field.date_time"),
dateTime.format(RFC_1123_DATE_TIME), true)
.addField(translate("moderation.warn.dm.field.reason"), reason, false)
.build()
);
}
public MessageAction warnList(MessageChannel channel, Map<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()
);
}
public MessageAction unwarn(MessageChannel channel, Member performer, int caseID, WarningEntry entry) {
return channel.sendMessage(
moderationEmbed(translate("moderation.unwarn.author"))
.addField(translate("moderation.unwarn.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.unwarn.field.case_id"), String.valueOf(caseID), true)
.addField(translate("moderation.unwarn.field.original_target"), entry.getWarned().getAsMention(), true)
.addField(translate("moderation.unwarn.field.original_performer"), entry.getPerformer().getAsMention(), true)
.addField(translate("moderation.unwarn.field.date_time"), entry.getDateTime().format(RFC_1123_DATE_TIME), true)
.addField(entry.getReason() != null ? translate("moderation.unwarn.field.reason") : null, entry.getReason(),
false)
.build()
);
}
public MessageAction addNote(MessageChannel channel, Member performer, Member target, String contents,
OffsetDateTime dateTime, int noteID) {
return channel.sendMessage(
moderationEmbed(translate("moderation.note.add.author"))
.addField(translate("moderation.note.add.field.performer"), performer.getUser().getAsMention(), true)
.addField(translate("moderation.note.add.field.target"), target.getUser().getAsMention(), true)
.addField(translate("moderation.note.add.field.note_id"), String.valueOf(noteID), true)
.addField(translate("moderation.note.add.field.date_time"), dateTime.format(RFC_1123_DATE_TIME), true)
.addField(translate("moderation.note.add.field.contents"), contents, false)
.build()
);
}
public MessageAction noteList(MessageChannel channel, Map<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()
);
}
public MessageAction removeNote(MessageChannel channel, Member performer, int noteID, NoteEntry entry) {
return channel.sendMessage(
moderationEmbed(translate("moderation.note.remove.author"))
.addField(translate("moderation.note.remove.field.performer"), performer.getAsMention(), true)
.addField(translate("moderation.note.remove.field.note_id"), String.valueOf(noteID), true)
.addField(translate("moderation.note.remove.field.original_target"), entry.getTarget().getAsMention(), true)
.addField(translate("moderation.note.remove.field.original_performer"), entry.getPerformer().getAsMention(),
true)
.addField(translate("moderation.note.remove.field.date_time"), entry.getDateTime().format(RFC_1123_DATE_TIME),
true)
.addField(translate("moderation.note.remove.field.contents"), entry.getContents(), false)
.build()
);
}
}

View File

@ -0,0 +1,54 @@
package sciwhiz12.janitor.msg;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.msg.json.RegularMessage;
import sciwhiz12.janitor.msg.substitution.IHasCustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.SubstitutionMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class RegularMessageBuilder implements IHasCustomSubstitutions<RegularMessageBuilder> {
private final RegularMessage message;
private final Map<String, Supplier<String>> customSubstitutions;
public RegularMessageBuilder(RegularMessage message, Map<String, Supplier<String>> customSubstitutions) {
this.message = message;
this.customSubstitutions = customSubstitutions;
}
public RegularMessageBuilder(RegularMessage message) {
this(message, new HashMap<>());
}
public RegularMessageBuilder(RegularMessageBuilder copy) {
this(copy.message, new HashMap<>(copy.customSubstitutions));
}
public RegularMessageBuilder apply(Consumer<RegularMessageBuilder> consumer) {
consumer.accept(this);
return this;
}
public RegularMessageBuilder with(final String argument, final Supplier<String> value) {
customSubstitutions.put(argument, value);
return this;
}
public MessageEmbed build(TranslationMap translations, SubstitutionMap substitutions) {
return message.create(translations, substitutions.with(customSubstitutions)).build();
}
public MessageEmbed build(JanitorBot bot) {
return build(bot.getTranslations(), bot.getSubstitutions());
}
public MessageAction send(JanitorBot bot, MessageChannel channel) {
return channel.sendMessage(build(bot));
}
}

View File

@ -1,32 +1,35 @@
package sciwhiz12.janitor.msg;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import sciwhiz12.janitor.JanitorBot;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static sciwhiz12.janitor.Logging.JANITOR;
import static sciwhiz12.janitor.Logging.TRANSLATIONS;
public class Translations {
private static final Gson GSON = new GsonBuilder().create();
public class TranslationMap {
public static final Pattern TRANSLATION_REGEX = Pattern.compile("<(.+?)>", CASE_INSENSITIVE);
private static final String DEFAULT_TRANSLATIONS_RESOURCE = "english.json";
private static final Type MAP_TYPE = new TypeToken<Map<String, String>>() {}.getType();
private static final TypeReference<Map<String, String>> MAP_TYPE = new TypeReference<>() {};
private final JanitorBot bot;
private final Path translationsFile;
private final Map<String, String> translations = new HashMap<>();
private final ObjectMapper jsonMapper = new ObjectMapper();
public Translations(JanitorBot bot, Path translationsFile) {
public TranslationMap(JanitorBot bot, Path translationsFile) {
this.bot = bot;
this.translationsFile = translationsFile;
loadTranslations();
@ -40,12 +43,11 @@ public class Translations {
}
try {
JANITOR.debug(TRANSLATIONS, "Loading translations from file {}", translationsFile);
Map<String, String> trans = GSON.fromJson(Files.newBufferedReader(translationsFile), MAP_TYPE);
Map<String, String> trans = jsonMapper.readValue(Files.newBufferedReader(translationsFile), MAP_TYPE);
translations.clear();
translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} translations from file {}", translations.size(), translationsFile);
}
catch (Exception e) {
} catch (Exception e) {
JANITOR.error(TRANSLATIONS, "Error while loading translations from file {}", translationsFile, e);
loadDefaultTranslations();
}
@ -55,14 +57,13 @@ public class Translations {
try {
JANITOR.debug(TRANSLATIONS, "Loading default english translations");
// noinspection UnstableApiUsage
Map<String, String> trans = GSON.fromJson(
Map<String, String> trans = jsonMapper.readValue(
new InputStreamReader(Resources.getResource(DEFAULT_TRANSLATIONS_RESOURCE).openStream()),
MAP_TYPE);
translations.clear();
translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} default english translations", translations.size());
}
catch (Exception e) {
} catch (Exception e) {
JANITOR.error(TRANSLATIONS, "Error while loading default english translations", e);
}
}
@ -71,7 +72,13 @@ public class Translations {
return Collections.unmodifiableMap(translations);
}
public String translate(String key, Object... args) {
return String.format(translations.getOrDefault(key, key), args);
public String translate(String text) {
final Matcher matcher = TRANSLATION_REGEX.matcher(text);
return matcher.replaceAll(
matchResult -> quoteReplacement(translations.getOrDefault(matchResult.group(1), matchResult.group(0))));
}
public JanitorBot getBot() {
return bot;
}
}

View File

@ -0,0 +1,43 @@
package sciwhiz12.janitor.msg.emote;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import sciwhiz12.janitor.JanitorBot;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
public class ReactionManager extends ListenerAdapter {
private final JanitorBot bot;
private final Map<Long, ReactionMessage> messageMap = new HashMap<>();
public ReactionManager(JanitorBot bot) {
this.bot = bot;
}
public ReactionMessage newMessage(Message message) {
if (messageMap.containsKey(message.getIdLong())) {
throw new IllegalArgumentException("Reaction message already exists for message with id " + message.getIdLong());
}
final ReactionMessage msg = new ReactionMessage(bot, message);
messageMap.put(message.getIdLong(), msg);
return msg;
}
public void removeMessage(long messageID) {
bot.getDiscord().removeEventListener(messageMap.remove(messageID));
}
public Map<Long, ReactionMessage> getRegisteredMessages() {
return messageMap;
}
@Override
public void onMessageDelete(@Nonnull MessageDeleteEvent event) {
if (messageMap.containsKey(event.getMessageIdLong())) {
bot.getDiscord().removeEventListener(messageMap.get(event.getMessageIdLong()));
}
}
}

View File

@ -0,0 +1,112 @@
package sciwhiz12.janitor.msg.emote;
import net.dv8tion.jda.api.entities.Emote;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageReaction.ReactionEmote;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.exceptions.ErrorHandler;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.ErrorResponse;
import sciwhiz12.janitor.JanitorBot;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.annotation.Nonnull;
import static net.dv8tion.jda.api.Permission.MESSAGE_MANAGE;
public class ReactionMessage extends ListenerAdapter {
private final JanitorBot bot;
private final Message message;
private final Map<ReactionEmote, IReactionListener> emotes = new LinkedHashMap<>();
private boolean removeEmotes = true;
private long ownerID;
private boolean onlyOwner;
public ReactionMessage(JanitorBot bot, Message message, boolean onlyOwner, long ownerID) {
this.bot = bot;
this.message = message;
this.ownerID = ownerID;
this.onlyOwner = onlyOwner;
}
public ReactionMessage(JanitorBot bot, Message message) {
this(bot, message, false, 0);
}
public ReactionMessage add(ReactionEmote emote, IReactionListener listener) {
emotes.put(emote, listener);
return this;
}
public ReactionMessage add(String emote, IReactionListener listener) {
return add(ReactionEmote.fromUnicode(emote, bot.getDiscord()), listener);
}
public ReactionMessage add(Emote emote, IReactionListener listener) {
return add(ReactionEmote.fromCustom(emote), listener);
}
public ReactionMessage removeEmotes(boolean remove) {
this.removeEmotes = remove;
return this;
}
public ReactionMessage owner(long ownerID) {
this.ownerID = ownerID;
this.onlyOwner = true;
return this;
}
public void create() {
for (ReactionEmote reaction : emotes.keySet()) {
if (reaction.isEmote()) {
message.addReaction(reaction.getEmote()).queue();
} else {
message.addReaction(reaction.getEmoji()).queue();
}
}
bot.getDiscord().addEventListener(this);
}
@Override
public void onMessageReactionAdd(@Nonnull MessageReactionAddEvent event) {
if (event.getMessageIdLong() != message.getIdLong()) return;
if (event.getUserIdLong() == bot.getDiscord().getSelfUser().getIdLong()) return;
if (onlyOwner && event.getUserIdLong() != ownerID) return;
emotes.keySet().stream()
.filter(emote -> event.getReactionEmote().equals(emote))
.forEach(emote -> emotes.get(emote).accept(this, event));
if (removeEmotes && (!event.isFromGuild()
|| event.getGuild().getSelfMember().hasPermission(event.getTextChannel(), MESSAGE_MANAGE))) {
event.retrieveUser()
.flatMap(user -> event.getReaction().removeReaction(user))
.queue(null, new ErrorHandler().ignore(ErrorResponse.UNKNOWN_MESSAGE));
}
}
public JanitorBot getBot() {
return bot;
}
public long getOwnerID() {
return ownerID;
}
public boolean isOwnerOnly() {
return onlyOwner;
}
public Map<ReactionEmote, IReactionListener> getListeners() {
return Collections.unmodifiableMap(emotes);
}
@FunctionalInterface
public interface IReactionListener extends BiConsumer<ReactionMessage, MessageReactionAddEvent> {
void accept(ReactionMessage message, MessageReactionAddEvent event);
}
}

View File

@ -0,0 +1,254 @@
package sciwhiz12.janitor.msg.json;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.primitives.Ints;
import joptsimple.internal.Strings;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import sciwhiz12.janitor.msg.TranslationMap;
import sciwhiz12.janitor.msg.substitution.CustomSubstitutions;
import sciwhiz12.janitor.msg.substitution.ISubstitutor;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.annotation.Nullable;
@JsonDeserialize(using = ListingMessageDeserializer.class)
public class ListingMessage {
@Nullable protected final String title;
@Nullable protected final String url;
@Nullable protected final String description;
@Nullable protected final String color;
@Nullable protected final String authorName;
@Nullable protected final String authorUrl;
@Nullable protected final String authorIconUrl;
@Nullable protected final String footerText;
@Nullable protected final String footerIconUrl;
@Nullable protected final String imageUrl;
@Nullable protected final String thumbnailUrl;
@Nullable protected final String emptyText;
protected final Entry entry;
protected final List<MessageEmbed.Field> beforeFields;
protected final List<MessageEmbed.Field> afterFields;
public ListingMessage(
@Nullable String title,
@Nullable String url,
@Nullable String description,
@Nullable String color,
@Nullable String authorName,
@Nullable String authorUrl,
@Nullable String authorIconUrl,
@Nullable String footerText,
@Nullable String footerIconUrl,
@Nullable String imageUrl,
@Nullable String thumbnailUrl,
@Nullable String emptyText,
Entry entry,
List<MessageEmbed.Field> beforeFields,
List<MessageEmbed.Field> afterFields
) {
this.title = title;
this.url = url;
this.description = description;
this.color = color;
this.authorName = authorName;
this.authorUrl = authorUrl;
this.authorIconUrl = authorIconUrl;
this.footerText = footerText;
this.footerIconUrl = footerIconUrl;
this.imageUrl = imageUrl;
this.thumbnailUrl = thumbnailUrl;
this.emptyText = emptyText;
this.entry = entry;
this.beforeFields = beforeFields;
this.afterFields = afterFields;
}
@Nullable
public String getTitle() {
return title;
}
@Nullable
public String getUrl() {
return url;
}
@Nullable
public String getDescription() {
return description;
}
@Nullable
public String getColor() {
return color;
}
@Nullable
public String getAuthorName() {
return authorName;
}
@Nullable
public String getAuthorUrl() {
return authorUrl;
}
@Nullable
public String getAuthorIconUrl() {
return authorIconUrl;
}
@Nullable
public String getFooterText() {
return footerText;
}
@Nullable
public String getFooterIconUrl() {
return footerIconUrl;
}
@Nullable
public String getImageUrl() {
return imageUrl;
}
@Nullable
public String getThumbnailUrl() {
return thumbnailUrl;
}
@Nullable
public String getEmptyText() {
return emptyText;
}
public Entry getEntry() {
return entry;
}
public List<MessageEmbed.Field> getBeforeFields() {
return beforeFields;
}
public List<MessageEmbed.Field> getAfterFields() {
return afterFields;
}
public <T> EmbedBuilder create(
TranslationMap translations,
ISubstitutor global,
Iterable<T> iterable,
BiConsumer<T, CustomSubstitutions> entryApplier
) {
final Function<String, String> func = str -> str != null ? global.substitute(translations.translate(str)) : null;
final EmbedBuilder builder = new EmbedBuilder();
builder.setTitle(func.apply(title), func.apply(url));
builder.setColor(parseColor(global.substitute(color)));
builder.setAuthor(func.apply(authorName), func.apply(authorUrl), func.apply(authorIconUrl));
builder.setDescription(func.apply(description));
builder.setImage(func.apply(imageUrl));
builder.setThumbnail(func.apply(thumbnailUrl));
builder.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC));
builder.setFooter(func.apply(footerText), func.apply(footerIconUrl));
for (MessageEmbed.Field field : beforeFields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
}
final CustomSubstitutions entrySubs = new CustomSubstitutions();
final Function<String, String> entryFunc = str -> str != null ? entrySubs.substitute(func.apply(str)) : null;
int count = 0;
for (T listEntry : iterable) {
entryApplier.accept(listEntry, entrySubs);
if (entry instanceof FieldEntry) {
FieldEntry fieldEntry = (FieldEntry) entry;
builder.addField(
entryFunc.apply(fieldEntry.getFieldName()),
entryFunc.apply(fieldEntry.getFieldValue()),
fieldEntry.isInline()
);
} else if (entry instanceof DescriptionEntry) {
DescriptionEntry descEntry = (DescriptionEntry) entry;
builder.getDescriptionBuilder().append(entryFunc.apply(descEntry.getDescription()));
builder.getDescriptionBuilder().append(descEntry.getJoiner());
}
count++;
}
if (count < 1) {
builder.getDescriptionBuilder().append(func.apply(emptyText));
}
for (MessageEmbed.Field field : afterFields) {
builder.addField(func.apply(field.getName()), func.apply(field.getValue()), field.isInline());
}
return builder;
}
private static int parseColor(String str) {
if (Strings.isNullOrEmpty(str)) return Role.DEFAULT_COLOR_RAW;
if (str.startsWith("0x")) {
// noinspection UnstableApiUsage
final Integer res = Ints.tryParse(str.substring(2), 16);
if (res != null) {
return res;
}
}
// noinspection UnstableApiUsage
final Integer res = Ints.tryParse(str, 10);
if (res != null) {
return res;
}
return Role.DEFAULT_COLOR_RAW;
}
public interface Entry {}
public static class DescriptionEntry implements Entry {
public static final String DEFAULT_JOINER = "\n";
private final String joiner;
private final String descriptionEntry;
public DescriptionEntry(@Nullable String joiner, String descriptionEntry) {
this.joiner = joiner != null ? joiner : DEFAULT_JOINER;
this.descriptionEntry = descriptionEntry;
}
public String getJoiner() {
return joiner;
}
public String getDescription() {
return descriptionEntry;
}
}
public static class FieldEntry implements Entry {
private final String fieldName;
private final String fieldValue;
private final boolean inline;
public FieldEntry(String fieldName, String fieldValue, boolean inline) {
this.fieldName = fieldName;
this.fieldValue = fieldValue;
this.inline = inline;
}
public String getFieldName() {
return fieldName;
}
public String getFieldValue() {
return fieldValue;
}
public boolean isInline() {
return inline;
}
}
}

View File

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

View File

@ -0,0 +1,212 @@
package sciwhiz12.janitor.msg.json;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.primitives.Ints;
import joptsimple.internal.Strings;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import sciwhiz12.janitor.msg.TranslationMap;
import sciwhiz12.janitor.msg.substitution.ISubstitutor;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;
import javax.annotation.Nullable;
@JsonDeserialize(using = RegularMessageDeserializer.class)
public class RegularMessage {
@Nullable
protected final String title;
@Nullable
protected final String url;
@Nullable
protected final String description;
@Nullable
protected final String color;
@Nullable
protected final String authorName;
@Nullable
protected final String authorUrl;
@Nullable
protected final String authorIconUrl;
@Nullable
protected final String footerText;
@Nullable
protected final String footerIconUrl;
@Nullable
protected final String imageUrl;
@Nullable
protected final String thumbnailUrl;
protected final List<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);
}
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,48 @@
package sciwhiz12.janitor.msg.substitution;
import org.apache.commons.collections4.TransformerUtils;
import org.apache.commons.collections4.map.DefaultedMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class CustomSubstitutions implements ISubstitutor, IHasCustomSubstitutions<CustomSubstitutions> {
private final Map<String, Supplier<String>> map;
public CustomSubstitutions(Map<String, Supplier<String>> map) {
this.map = map;
}
public CustomSubstitutions() {
this(new HashMap<>());
}
@Override
public String substitute(String text) {
return SubstitutionMap.substitute(text, map);
}
public CustomSubstitutions apply(Consumer<CustomSubstitutions> consumer) {
consumer.accept(this);
return this;
}
public CustomSubstitutions with(final String argument, final Supplier<String> value) {
map.put(argument, value);
return this;
}
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(map));
}
public Map<String, Supplier<String>> getMap() {
return map;
}
}

View File

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

View File

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

View File

@ -0,0 +1,70 @@
package sciwhiz12.janitor.msg.substitution;
import org.apache.commons.collections4.TransformerUtils;
import org.apache.commons.collections4.map.DefaultedMap;
import sciwhiz12.janitor.JanitorBot;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static sciwhiz12.janitor.msg.MessageHelper.DATE_TIME_FORMAT;
public class SubstitutionMap implements ISubstitutor {
public static final Pattern ARGUMENT_REGEX = Pattern.compile("\\$\\{(.+?)}", CASE_INSENSITIVE);
public static final Pattern NULL_ARGUMENT_REGEX = Pattern.compile("nullcheck;(.+?);(.+)", CASE_INSENSITIVE);
public static String substitute(String text, Map<String, Supplier<String>> arguments) {
final Matcher matcher = ARGUMENT_REGEX.matcher(text);
return matcher.replaceAll(matchResult -> {
final Matcher nullMatcher = NULL_ARGUMENT_REGEX.matcher(matchResult.group(1));
if (nullMatcher.matches()) {
final String grp1 = nullMatcher.group(1);
final String str = arguments.containsKey(grp1) ? arguments.get(grp1).get() : null;
return str != null ?
quoteReplacement(str) :
quoteReplacement(arguments.getOrDefault(nullMatcher.group(2), () -> nullMatcher.group(2)).get());
}
return quoteReplacement(arguments.getOrDefault(matchResult.group(1), () -> matchResult.group(0)).get());
});
}
private final JanitorBot bot;
private final Map<String, Supplier<String>> defaultSubstitutions = new HashMap<>();
public SubstitutionMap(JanitorBot bot) {
this.bot = bot;
defaultSubstitutions.put("time.now", () -> OffsetDateTime.now(ZoneOffset.UTC).format(DATE_TIME_FORMAT));
defaultSubstitutions.put("moderation.color", () -> "0xF1BD25");
defaultSubstitutions.put("moderation.icon_url",
() -> "https://cdn.discordapp.com/attachments/738478941760782526/760463743330549760/gavel.png");
defaultSubstitutions.put("general.error.color", () -> "0xF73132");
}
public JanitorBot getBot() {
return bot;
}
public String substitute(String text) {
return SubstitutionMap.substitute(text, defaultSubstitutions);
}
public String with(String text, Map<String, Supplier<String>> substitutions) {
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,8 +1,6 @@
package sciwhiz12.janitor.storage;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.dv8tion.jda.api.entities.Guild;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.Logging;
@ -22,8 +20,6 @@ import static java.nio.file.StandardOpenOption.*;
* A storage system for guild-specific data.
*/
public class GuildStorage {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final JanitorBot bot;
private final Path mainFolder;
private final Map<Long, Map<String, InnerStorage<?>>> guildStorages = new HashMap<>();

View File

@ -1,5 +1,6 @@
package sciwhiz12.janitor.storage;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
@ -7,7 +8,7 @@ public interface IStorage {
boolean dirty();
void write(Writer output);
void write(Writer output) throws IOException;
void read(Reader input);
void read(Reader input) throws IOException;
}

View File

@ -1,30 +1,35 @@
package sciwhiz12.janitor.storage;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public abstract class JsonStorage extends AbstractStorage {
public static final Gson GSON = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
protected final ObjectMapper jsonMapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
public abstract JsonElement save();
protected JsonStorage() {
initialize(jsonMapper);
}
public abstract void load(JsonElement object);
protected void initialize(ObjectMapper mapper) {}
public abstract JsonNode save(ObjectMapper mapper);
public abstract void load(JsonNode object, ObjectMapper mapper) throws IOException;
@Override
public void write(Writer input) {
GSON.toJson(save(), input);
public void write(Writer input) throws IOException {
jsonMapper.writeTree(jsonMapper.createGenerator(input), save(jsonMapper));
}
@Override
public void read(Reader input) {
load(JsonParser.parseReader(input));
public void read(Reader input) throws IOException {
load(jsonMapper.readTree(input), jsonMapper);
}
}

View File

@ -37,8 +37,7 @@ public class StringReaderUtil {
}
private static final char SYNTAX_ESCAPE = '\\';
private static final char SYNTAX_DOUBLE_QUOTE = '"';
private static final char SYNTAX_SINGLE_QUOTE = '\'';
public static String readStringUntil(StringReader reader, char terminator) throws CommandSyntaxException {
final StringBuilder result = new StringBuilder();
boolean escaped = false;
@ -50,7 +49,8 @@ public class StringReaderUtil {
escaped = false;
} else {
reader.setCursor(reader.getCursor() - 1);
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidEscape().createWithContext(reader, String.valueOf(c));
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidEscape()
.createWithContext(reader, String.valueOf(c));
}
} else if (c == SYNTAX_ESCAPE) {
escaped = true;

View File

@ -55,8 +55,8 @@ public class Util {
return user.getName().concat("#").concat(user.getDiscriminator());
}
public static <Success, Error> BiConsumer<Success, Error> handle(final Consumer<Success> success,
final Consumer<Error> exceptionally) {
public static <Success, Err> BiConsumer<Success, Err> handle(final Consumer<Success> success,
final Consumer<Err> exceptionally) {
return (suc, ex) -> {
if (ex == null) {
success.accept(suc);

View File

@ -1,100 +1,100 @@
{
"general.guild_only_command.title": "Guild only command!",
"general.guild_only_command.desc": "This command can only be run in a guild channel.",
"general.insufficient_permissions.title": "I have insufficient permissions!",
"general.insufficient_permissions.desc": "I do not have sufficient permissions to carry out this action!\nPlease contact your server admins if you believe this is in error.",
"general.insufficient_permissions.field.permissions": "Required permissions",
"general.ambiguous_member.title": "Ambiguous member argument!",
"general.ambiguous_member.desc": "The name you have specified is too ambiguous (leads to more than 1 member)!\nPlease narrow down the specified name until it can uniquely identify a member of this guild.",
"general.cannot_interact.title": "Member is higher than me!",
"general.cannot_interact.desc": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.",
"general.cannot_interact.field.target": "Target",
"general.cannot_action_self.title": "Cannot act against myself!",
"general.cannot_action_self.desc": "Cannot perform action against myself, as that would be counter-intuitive.",
"general.cannot_action_performer.title": "Performer cannot act against self!",
"general.cannot_action_performer.desc": "You cannot perform this action against yourself.",
"general.cannot_action_performer.field.performer": "Performer/Target",
"general.error.guild_only_command.title": "Guild only command!",
"general.error.guild_only_command.description": "This command can only be run in a guild channel.",
"general.error.ambiguous_member.title": "Ambiguous member argument!",
"general.error.ambiguous_member.description": "The name you have specified is too ambiguous (leads to more than 1 member)!\nPlease narrow down the specified name until it can uniquely identify a member of this guild.",
"general.error.insufficient_permissions.title": "I have insufficient permissions!",
"general.error.insufficient_permissions.description": "I do not have sufficient permissions to carry out this action!\nPlease contact your server admins if you believe this is in error.",
"general.error.insufficient_permissions.field.permissions": "Required permissions",
"general.error.cannot_interact.title": "Member is higher than me!",
"general.error.cannot_interact.description": "Cannot perform action on the given member, likely due to me being lower in the role hierarchy.",
"general.error.cannot_interact.field.target": "Target",
"general.error.cannot_action_self.title": "Cannot act against myself!",
"general.error.cannot_action_self.description": "Cannot perform action against myself, as that would be counter-intuitive.",
"general.error.cannot_action_performer.title": "Performer cannot act against self!",
"general.error.cannot_action_performer.description": "You cannot perform this action against yourself.",
"general.error.cannot_action_performer.field.performer": "Performer/Target",
"moderation.insufficient_permissions.title": "Insufficient permissions.",
"moderation.insufficient_permissions.desc": "The performer of this command has insufficient permissions to use this command.",
"moderation.insufficient_permissions.description": "The performer of this command has insufficient permissions to use this command.",
"moderation.insufficient_permissions.field.performer": "Performer",
"moderation.insufficient_permissions.field.permissions": "Required permissions",
"moderation.cannot_interact.title": "Cannot moderate Target.",
"moderation.cannot_interact.desc": "The performer of this command cannot moderate the target user, likely due to being lower in the role hierarchy.",
"moderation.cannot_interact.description": "The performer of this command cannot moderate the target user, likely due to being lower in the role hierarchy.",
"moderation.cannot_interact.field.performer": "Performer",
"moderation.cannot_interact.field.target": "Target",
"moderation.kick.info.author": "Kicked user from server.",
"moderation.kick.info.field.performer": "Performer",
"moderation.kick.info.field.target": "Target",
"moderation.kick.info.field.reason": "Reason",
"moderation.kick.info.field.sent_private_message": "Sent DM",
"moderation.kick.info.field.private_message": "Sent DM",
"moderation.kick.info.field.reason.name": "Reason",
"moderation.kick.info.field.reason.value": "${nullcheck;reason;_No reason specified._}",
"moderation.kick.dm.title": "You were kicked from this server.",
"moderation.kick.dm.field.performer": "Moderator",
"moderation.kick.dm.field.reason": "Reason",
"moderation.kick.dm.field.reason.name": "Reason",
"moderation.kick.dm.field.reason.value": "${nullcheck;reason;_No reason specified._}",
"moderation.ban.info.author": "Banned user from server.",
"moderation.ban.info.field.performer": "Performer",
"moderation.ban.info.field.target": "Target",
"moderation.ban.info.field.reason": "Reason",
"moderation.ban.info.field.sent_private_message": "Sent DM",
"moderation.ban.info.field.delete_duration": "Message Deletion",
"moderation.ban.info.field.delete_duration.value": "%s day(s)",
"moderation.ban.info.field.private_message": "Sent DM",
"moderation.ban.info.field.delete_duration.name": "Message Deletion",
"moderation.ban.info.field.delete_duration.value": "${delete_duration} day(s)",
"moderation.ban.info.field.reason.name": "Reason",
"moderation.ban.info.field.reason.value": "${nullcheck;reason;_No reason specified._}",
"moderation.ban.dm.title": "You were banned from this server.",
"moderation.ban.dm.field.performer": "Moderator",
"moderation.ban.dm.field.reason": "Reason",
"moderation.ban.dm.field.reason.name": "Reason",
"moderation.ban.dm.field.reason.value": "${nullcheck;reason;_No reason specified._}",
"moderation.unban.info.author": "Unbanned user from server.",
"moderation.unban.info.field.performer": "Performer",
"moderation.unban.info.field.target": "Target",
"moderation.warn.info.author": "Warned user.",
"moderation.warn.info.field.performer": "Performer",
"moderation.warn.info.field.target": "Target",
"moderation.warn.info.field.sent_private_message": "Sent DM",
"moderation.warn.info.field.case_id": "Case ID",
"moderation.warn.info.field.reason.name": "Reason",
"moderation.warn.info.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"moderation.warn.info.field.private_message": "Sent DM",
"moderation.warn.info.field.date_time": "Date & Time",
"moderation.warn.info.field.reason": "Reason",
"moderation.warn.dm.title": "You were warned by a moderator.",
"moderation.warn.dm.field.performer": "Moderator",
"moderation.warn.dm.field.date_time": "Date & Time",
"moderation.warn.dm.field.reason": "Reason",
"moderation.warnlist.author": "Listing of Warnings",
"moderation.warnlist.empty": "**_No warnings logged matching your query._**",
"moderation.warnlist.entry": "**Case #%1$s**: Warned %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Reason:_ %5$s",
"moderation.warnlist.entry.no_reason": "_no reason specified_",
"moderation.unwarn.author": "Removed warning from user.",
"moderation.unwarn.field.performer": "Performer",
"moderation.unwarn.field.original_target": "Original Target",
"moderation.unwarn.field.original_performer": "Original Performer",
"moderation.unwarn.field.case_id": "Case ID",
"moderation.unwarn.field.date_time": "Date & Time",
"moderation.unwarn.field.reason": "Reason",
"moderation.warn.dm.field.reason.name": "Reason",
"moderation.warn.dm.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"moderation.unwarn.info.author": "Removed warning from user.",
"moderation.unwarn.info.field.performer": "Performer",
"moderation.unwarn.info.field.original_target": "Original Target",
"moderation.unwarn.info.field.original_performer": "Original Performer",
"moderation.unwarn.info.field.case_id": "Case ID",
"moderation.unwarn.info.field.date_time": "Date & Time",
"moderation.unwarn.info.field.reason.name": "Reason",
"moderation.unwarn.info.field.reason.value": "${nullcheck;warning_entry.reason;_No reason specified._}",
"moderation.unwarn.no_case_found.title": "No warning found.",
"moderation.unwarn.no_case_found.desc": "No warning with that case ID was found.",
"moderation.unwarn.no_case_found.description": "No warning with that case ID was found.",
"moderation.unwarn.no_case_found.field.performer": "Performer",
"moderation.unwarn.no_case_found.field.case_id": "Case ID",
"moderation.unwarn.cannot_unwarn_self.title": "Cannot remove warning from self.",
"moderation.unwarn.cannot_unwarn_self.desc": "Performer cannot remove a warning from themselves.",
"moderation.unwarn.cannot_unwarn_self.description": "Performer cannot remove a warning from themselves.",
"moderation.unwarn.cannot_unwarn_self.field.performer": "Performer/Original Target",
"moderation.unwarn.cannot_unwarn_self.field.original_performer": "Original Performer",
"moderation.unwarn.cannot_unwarn_self.field.case_id": "Case ID",
"moderation.warn.cannot_warn_mods.title": "Cannot warn moderators.",
"moderation.warn.cannot_warn_mods.desc": "Moderators cannot issue warnings to other moderators.",
"moderation.warn.cannot_warn_mods.description": "Moderators cannot issue warnings to other moderators.",
"moderation.warn.cannot_warn_mods.field.performer": "Performer",
"moderation.warn.cannot_warn_mods.field.target": "Target",
"moderation.warn.cannot_remove_higher_mod.title": "Cannot remove warning issued by higher-ranked moderator.",
"moderation.warn.cannot_remove_higher_mod.desc": "The performer cannot remove this warning, as this was issued by a higher-ranking moderator.",
"moderation.warn.cannot_remove_higher_mod.description": "The performer cannot remove this warning, as this was issued by a higher-ranking moderator.",
"moderation.warn.cannot_remove_higher_mod.field.performer": "Performer",
"moderation.warn.cannot_remove_higher_mod.field.original_performer": "Original Performer",
"moderation.warn.cannot_remove_higher_mod.field.case_id": "Case ID",
"moderation.note.max_amount_of_notes.title": "Max notes reached.",
"moderation.note.max_amount_of_notes.desc": "The performer has reached the maximum amount of notes for the target user.",
"moderation.note.max_amount_of_notes.description": "The performer has reached the maximum amount of notes for the target user.",
"moderation.note.max_amount_of_notes.field.performer": "Performer",
"moderation.note.max_amount_of_notes.field.target": "Target",
"moderation.note.max_amount_of_notes.field.amount": "(Max.) Amount",
"moderation.note.no_note_found.title": "No note found.",
"moderation.note.no_note_found.desc": "No note with that note ID was found.",
"moderation.note.no_note_found.description": "No note with that note ID was found.",
"moderation.note.no_note_found.field.performer": "Performer",
"moderation.note.no_note_found.field.note_id": "Note ID",
"moderation.note.list.author": "Listing of Notes",
"moderation.note.list.empty": "**_No recorded notes matching your query._**",
"moderation.note.list.entry": "**#%1$s**: for %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Text:_ %5$s",
"moderation.note.add.author": "Recorded note for user.",
"moderation.note.add.field.performer": "Performer",
"moderation.note.add.field.target": "Target",
@ -103,9 +103,15 @@
"moderation.note.add.field.contents": "Text",
"moderation.note.remove.author": "Removed note.",
"moderation.note.remove.field.performer": "Performer",
"moderation.note.remove.field.original_target": "Original Target",
"moderation.note.remove.field.original_performer": "Original Performer",
"moderation.note.remove.field.original_target": "Original Target",
"moderation.note.remove.field.note_id": "Note ID",
"moderation.note.remove.field.date_time": "Date & Time",
"moderation.note.remove.field.contents": "Text"
"moderation.note.remove.field.contents": "Text",
"moderation.warnlist.author": "Listing of Warnings",
"moderation.warnlist.empty": "**_No warnings logged matching your query._**",
"moderation.warnlist.entry": "**Case #${warning_entry.case_id}**: Warned ${warning_entry.warned.mention} by ${warning_entry.performer.mention} \n - _Date & Time:_ ${warning_entry.date_time} \n - _Reason:_ ${nullcheck;warning_entry.reason;_No reason specified._}",
"moderation.note.list.author": "Listing of Notes (Page ${page.current}/${page.max})",
"moderation.note.list.empty": "**_No recorded notes matching your query._**",
"moderation.note.list.entry": "**#${note_entry.note_id}**: for ${note_entry.target.mention} by ${note_entry.performer.mention} \n - _Date & Time:_ ${note_entry.date_time} \n - _Text:_ ${note_entry.contents}"
}

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,28 @@
[
"general/error/ambiguous_member",
"general/error/guild_only_command",
"general/error/insufficient_permissions",
"general/error/cannot_interact",
"general/error/cannot_action_self",
"general/error/cannot_action_performer",
"moderation/error/cannot_interact",
"moderation/error/insufficient_permissions",
"moderation/kick/info",
"moderation/kick/dm",
"moderation/ban/info",
"moderation/ban/dm",
"moderation/unban/info",
"moderation/warn/info",
"moderation/warn/dm",
"moderation/warn/list",
"moderation/unwarn/info",
"moderation/error/unwarn/no_case_found",
"moderation/error/unwarn/cannot_unwarn_self",
"moderation/error/unwarn/cannot_remove_higher_mod",
"moderation/error/warn/cannot_warn_mods",
"moderation/error/note/max_amount_of_notes",
"moderation/error/note/no_note_found",
"moderation/note/add",
"moderation/note/remove",
"moderation/note/list"
]

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,13 @@
{
"type": "listing",
"color": "${moderation.color}",
"author": {
"name": "<moderation.note.list.author>",
"icon_url": "${moderation.icon_url}"
},
"entry": {
"type": "description",
"text": "<moderation.note.list.entry>"
},
"empty": "<moderation.note.list.empty>"
}

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
}
]
}

View File

@ -0,0 +1,13 @@
{
"type": "listing",
"color": "${moderation.color}",
"author": {
"name": "<moderation.warnlist.author>",
"icon_url": "${moderation.icon_url}"
},
"entry": {
"type": "description",
"text": "<moderation.warnlist.entry>"
},
"empty": "<moderation.warnlist.empty>"
}