1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-09-20 02:54: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: 'com.electronwill.night-config', name: 'toml', version: nightconfig_version
implementation group: 'net.sf.jopt-simple', name: 'jopt-simple', version: jopt_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.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: '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: 'ch.qos.logback', name: 'logback-classic', version: logback_version
implementation group: 'com.mojang', name: 'brigadier', version: brigadier_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 nightconfig_version=3.6.3
jopt_version=6.0-alpha-3 jopt_version=6.0-alpha-3
guava_version=29.0-jre guava_version=29.0-jre
gson_version=2.8.6 jackson_version=2.11.2
log4j_bridge_version=2.13.3 log4j_bridge_version=2.13.3
logback_version=1.3.0-alpha5 logback_version=1.3.0-alpha5
brigadier_version=1.0.17 brigadier_version=1.0.17

View File

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

View File

@ -1,5 +1,6 @@
package sciwhiz12.janitor; package sciwhiz12.janitor;
import com.google.common.base.Preconditions;
import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Activity;
@ -12,7 +13,6 @@ import sciwhiz12.janitor.config.BotOptions;
import java.util.EnumSet; import java.util.EnumSet;
import static com.google.common.base.Preconditions.checkArgument;
import static sciwhiz12.janitor.Logging.JANITOR; import static sciwhiz12.janitor.Logging.JANITOR;
public class BotStartup { public class BotStartup {
@ -21,7 +21,7 @@ public class BotStartup {
BotOptions options = new BotOptions(args); BotOptions options = new BotOptions(args);
BotConfig config = new BotConfig(options); 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..."); JANITOR.info("Building bot instance and connecting to Discord...");
@ -37,8 +37,7 @@ public class BotStartup {
} }
}) })
.build(); .build();
} } catch (Exception ex) {
catch (Exception ex) {
JANITOR.error("Error while building Discord connection", 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.commands.CommandRegistry;
import sciwhiz12.janitor.config.BotConfig; import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.msg.Messages; 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.storage.GuildStorage;
import sciwhiz12.janitor.utils.Util; import sciwhiz12.janitor.utils.Util;
@ -22,22 +24,27 @@ import static sciwhiz12.janitor.Logging.STATUS;
public class JanitorBot { public class JanitorBot {
private final JDA discord; private final JDA discord;
private final BotConfig config; private final BotConfig config;
private final Messages messages; private final BotConsole console;
private BotConsole console;
private final GuildStorage storage; private final GuildStorage storage;
private final GuildStorage.SavingThread storageSavingThread; private final GuildStorage.SavingThread storageSavingThread;
private CommandRegistry cmdRegistry; private final CommandRegistry cmdRegistry;
private Translations translations; private final TranslationMap translations;
private final SubstitutionMap substitutions;
private final Messages messages;
private final ReactionManager reactions;
public JanitorBot(JDA discord, BotConfig config) { public JanitorBot(JDA discord, BotConfig config) {
this.config = config; this.config = config;
this.discord = discord;
this.console = new BotConsole(this, System.in); this.console = new BotConsole(this, System.in);
this.storage = new GuildStorage(this, Path.of(config.STORAGE_PATH.get())); this.storage = new GuildStorage(this, Path.of(config.STORAGE_PATH.get()));
this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix()); this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix());
this.discord = discord; this.translations = new TranslationMap(this, config.getTranslationsFile());
this.translations = new Translations(this, config.getTranslationsFile()); this.substitutions = new SubstitutionMap(this);
this.messages = new Messages(this); this.messages = new Messages(this, config.getTranslationsFile());
discord.addEventListener(cmdRegistry); 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.getPresence().setPresence(OnlineStatus.ONLINE, Activity.playing(" n' sweeping n' testing!"));
discord.getGuilds().forEach(Guild::loadMembers); discord.getGuilds().forEach(Guild::loadMembers);
JANITOR.info("Ready!"); JANITOR.info("Ready!");
@ -65,7 +72,9 @@ public class JanitorBot {
return this.config; return this.config;
} }
public Messages getMessages() { return this.messages; } public Messages getMessages() {
return messages;
}
public GuildStorage getStorage() { return this.storage; } public GuildStorage getStorage() { return this.storage; }
@ -73,10 +82,14 @@ public class JanitorBot {
return this.cmdRegistry; return this.cmdRegistry;
} }
public Translations getTranslations() { public TranslationMap getTranslations() {
return this.translations; return this.translations;
} }
public ReactionManager getReactionManager() {
return this.reactions;
}
public void shutdown() { public void shutdown() {
JANITOR.info(STATUS, "Shutting down!"); JANITOR.info(STATUS, "Shutting down!");
getConfig().getOwnerID() getConfig().getOwnerID()
@ -102,4 +115,8 @@ public class JanitorBot {
storage.save(); storage.save();
console.stop(); 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 STATUS = MarkerFactory.getMarker("STATUS");
public static final Marker COMMANDS = MarkerFactory.getMarker("COMMANDS"); public static final Marker COMMANDS = MarkerFactory.getMarker("COMMANDS");
public static final Marker TRANSLATIONS = MarkerFactory.getMarker("TRANSLATIONS"); 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 Marker STORAGE = MarkerFactory.getMarker("STORAGE");
public static final Logger JANITOR = LoggerFactory.getLogger("janitor"); public static final Logger JANITOR = LoggerFactory.getLogger("janitor");

View File

@ -84,8 +84,7 @@ public class CommandRegistry implements EventListener {
} }
JANITOR.debug(COMMANDS, "Executing command."); JANITOR.debug(COMMANDS, "Executing command.");
dispatcher.execute(parseResults); dispatcher.execute(parseResults);
} } catch (CommandSyntaxException ex) {
catch (CommandSyntaxException ex) {
JANITOR.error(COMMANDS, "Error while parsing message and executing command", 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; import java.util.stream.Collectors;
public class GuildMemberArgument implements ArgumentType<GuildMemberArgument.IMemberProvider> { 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 UNKNOWN_MEMBER_IDENTIFIER = new SimpleCommandExceptionType(
public static final SimpleCommandExceptionType MULTIPLE_MEMBERS = new SimpleCommandExceptionType(new LiteralMessage("Too many users, when only one is needed")); 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]+)>"); 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 { public List<Member> fromGuild(Guild guild) throws CommandSyntaxException {
final String nameLowercase = name.toLowerCase(Locale.ROOT); final String nameLowercase = name.toLowerCase(Locale.ROOT);
final List<Member> members = guild.getMembers().stream() 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()); .collect(Collectors.toList());
if (!multiple && members.size() > 1) { if (!multiple && members.size() > 1) {
throw MULTIPLE_MEMBERS.create(); throw MULTIPLE_MEMBERS.create();

View File

@ -24,9 +24,8 @@ public class HelloCommand extends BaseCommand {
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() { public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("greet") return literal("greet")
.then( .then(argument("member", GuildMemberArgument.member())
argument("member", GuildMemberArgument.member()) .executes(this::run)
.executes(this::run)
); );
} }
@ -35,11 +34,22 @@ public class HelloCommand extends BaseCommand {
final List<Member> memberList = getMembers("member", ctx).fromGuild(ctx.getSource().getGuild()); final List<Member> memberList = getMembers("member", ctx).fromGuild(ctx.getSource().getGuild());
if (memberList.size() == 1) { if (memberList.size() == 1) {
final Member member = memberList.get(0); final Member member = memberList.get(0);
ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!") ctx.getSource().getChannel().sendMessage("Hello " + member.getAsMention() + "!").queue(
.queue( success -> {
success -> JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())), JANITOR.debug("Sent greeting message to {}, on cmd of {}", Util.toString(member.getUser()),
err -> JANITOR.error("Error while sending greeting message to {}, on cmd of {}", Util.toString(member.getUser()), Util.toString(ctx.getSource().getAuthor())) 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()))
);
} }
} }
return 1; return 1;

View File

@ -26,7 +26,8 @@ public class OKCommand extends BaseCommand {
.addReaction("\uD83D\uDC4C") .addReaction("\uD83D\uDC4C")
.queue( .queue(
success -> JANITOR.debug("Reacted :ok_hand: to {}'s message", Util.toString(ctx.getSource().getAuthor())), 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; 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.Member;
import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.ModerationHelper; import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; 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 { 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 {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue(); messages().getRegularMessage("general/error/guild_only_command")
return; .apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild()); 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 Member target = members.get(0);
if (guild.getSelfMember().equals(target)) if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue(); messages().getRegularMessage("general/error/cannot_action_self")
else if (performer.equals(target)) .apply(MessageHelper.member("performer", performer))
messages().GENERAL.cannotActionPerformer(channel, performer).queue(); .send(getBot(), channel).queue();
else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION))
messages().GENERAL.insufficientPermissions(channel, BAN_PERMISSION).queue(); } else if (performer.equals(target)) {
else if (!guild.getSelfMember().canInteract(target)) messages().getRegularMessage("general/error/cannot_action_performer")
messages().GENERAL.cannotInteract(channel, target).queue(); .apply(MessageHelper.member("performer", performer))
else if (!performer.hasPermission(BAN_PERMISSION)) .send(getBot(), channel).queue();
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, BAN_PERMISSION).queue();
else if (!performer.canInteract(target)) } else if (!guild.getSelfMember().hasPermission(BAN_PERMISSION)) {
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); messages().getRegularMessage("general/error/insufficient_permissions")
else .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() 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() .mapToResult()
.flatMap(res -> ModerationHelper.banUser(target.getGuild(), performer, target, days, reason) .flatMap(res ->
.flatMap( ModerationHelper.banUser(target.getGuild(), performer, target, days, reason)
v -> messages().MODERATION.banUser(channel, performer, target, reason, days, res.isSuccess()))) .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(); .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.Member;
import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.CommandHelper; import sciwhiz12.janitor.commands.util.CommandHelper;
import sciwhiz12.janitor.commands.util.ModerationHelper; import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.StringArgumentType.getString; import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; 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 { private int runWithReason(CommandContext<MessageReceivedEvent> ctx, @Nullable String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild()); final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) { if (members.size() < 1) { return 1; }
return 1;
}
final Member target = members.get(0); final Member target = members.get(0);
if (guild.getSelfMember().equals(target))
messages().GENERAL.cannotActionSelf(channel).queue(); if (guild.getSelfMember().equals(target)) {
else if (performer.equals(target)) messages().getRegularMessage("general/error/cannot_action_self")
messages().GENERAL.cannotActionPerformer(channel, performer).queue(); .apply(MessageHelper.member("performer", performer))
else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) .send(getBot(), channel).queue();
messages().GENERAL.insufficientPermissions(channel, KICK_PERMISSION).queue();
else if (!guild.getSelfMember().canInteract(target)) } else if (performer.equals(target)) {
messages().GENERAL.cannotInteract(channel, target).queue(); messages().getRegularMessage("general/error/cannot_action_performer")
else if (!performer.hasPermission(KICK_PERMISSION)) .apply(MessageHelper.member("performer", performer))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, KICK_PERMISSION).queue(); .send(getBot(), channel).queue();
else if (!performer.canInteract(target))
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); } else if (!guild.getSelfMember().hasPermission(KICK_PERMISSION)) {
else 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() 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() .mapToResult()
.flatMap(res -> ModerationHelper.kickUser(target.getGuild(), performer, target, reason) .flatMap(res -> ModerationHelper.kickUser(target.getGuild(), performer, target, reason)
.flatMap( .flatMap(v -> messages().getRegularMessage("moderation/kick/info")
v -> messages().MODERATION.kickUser(channel, performer, target, reason, res.isSuccess()))) .apply(MessageHelper.member("performer", performer))
.apply(MessageHelper.member("target", target))
.with("private_message", () -> res.isSuccess() ? "" : "")
.with("reason", () -> reason)
.send(getBot(), channel)
)
)
.queue(); .queue();
}
return 1; return 1;
} }
} }

View File

@ -1,6 +1,6 @@
package sciwhiz12.janitor.commands.moderation; 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.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; 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.Member;
import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.notes.NoteEntry; import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.notes.NoteStorage; import sciwhiz12.janitor.moderation.notes.NoteStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.annotation.Nullable;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger; import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; 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.moderation.NoteCommand.ModeratorFilter.*;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal; import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
import static sciwhiz12.janitor.msg.MessageHelper.*;
public class NoteCommand extends BaseCommand { public class NoteCommand extends BaseCommand {
public static EnumSet<Permission> NOTE_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS); 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 { private int addNote(CommandContext<MessageReceivedEvent> ctx, String noteContents) throws CommandSyntaxException {
final MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return 1;
} }
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final Guild guild = performer.getGuild(); final Guild guild = performer.getGuild();
final MessageChannel channel = ctx.getSource().getChannel();
final List<Member> members = getMembers("target", ctx).fromGuild(guild); final List<Member> members = getMembers("target", ctx).fromGuild(guild);
if (members.size() < 1) return 1; if (members.size() < 1) return 1;
final Member target = members.get(0); final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC); final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target)) if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue(); messages().getRegularMessage("general/error/cannot_action_self")
else if (performer.equals(target)) .apply(MessageHelper.member("performer", performer))
messages().GENERAL.cannotActionPerformer(channel, performer).queue(); .send(getBot(), channel).queue();
else if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue(); } else if (performer.equals(target)) {
else { 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 NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get(); final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get();
if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) { 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 { } else {
int noteID = storage.addNote(new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents)); final NoteEntry entry = new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents);
messages().MODERATION.addNote(channel, performer, target, noteContents, dateTime, noteID).queue(); 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; return 1;
@ -127,9 +154,12 @@ public class NoteCommand extends BaseCommand {
private int listNotes(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, ModeratorFilter modFilter) private int listNotes(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, ModeratorFilter modFilter)
throws CommandSyntaxException { throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel(); final MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
@ -141,7 +171,10 @@ public class NoteCommand extends BaseCommand {
if (members.size() < 1) return 1; if (members.size() < 1) return 1;
final Member target = members.get(0); final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) { 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; return 1;
} }
predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong()); predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong());
@ -156,44 +189,73 @@ public class NoteCommand extends BaseCommand {
case PERFORMER: { case PERFORMER: {
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == performer.getIdLong()); 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)) } else {
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue(); messages().<Map.Entry<Integer, NoteEntry>>getListingMessage("moderation/note/list")
else .apply(MessageHelper.member("performer", performer))
messages().MODERATION.noteList(channel, NoteStorage.get(getBot().getStorage(), guild) .amountPerPage(8)
.getNotes() .setEntryApplier((entry, subs) -> subs
.entrySet().stream() .with("note_entry.note_id", () -> String.valueOf(entry.getKey()))
.filter(predicate) .apply(user("note_entry.performer", entry.getValue().getPerformer()))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)) .apply(user("note_entry.target", entry.getValue().getTarget()))
).queue(); .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)
.sorted(Comparator.<Map.Entry<Integer, NoteEntry>>comparingInt(Map.Entry::getKey).reversed())
.collect(ImmutableList.toImmutableList())
);
}
return 1; return 1;
} }
private int removeNote(CommandContext<MessageReceivedEvent> ctx, int noteID) { private int removeNote(CommandContext<MessageReceivedEvent> ctx, int noteID) {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); 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)) } else {
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild); final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
@Nullable @Nullable
final NoteEntry entry = storage.getNote(noteID); final NoteEntry entry = storage.getNote(noteID);
if (entry == null) if (entry == null) {
messages().MODERATION.ERRORS.noNoteFound(channel, performer, noteID).queue(); messages().getRegularMessage("moderation/note/add")
else { .apply(MessageHelper.member("performer", performer))
.with("note_id", () -> String.valueOf(noteID))
.send(getBot(), channel).queue();
} else {
storage.removeNote(noteID); 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; 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.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.commands.util.ModerationHelper; import sciwhiz12.janitor.commands.util.ModerationHelper;
import sciwhiz12.janitor.msg.MessageHelper;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Locale; import java.util.Locale;
@ -49,7 +50,10 @@ public class UnbanCommand extends BaseCommand {
void realNamedRun(CommandContext<MessageReceivedEvent> ctx) { void realNamedRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
@ -62,10 +66,14 @@ public class UnbanCommand extends BaseCommand {
.startsWith(username)) .startsWith(username))
.collect(Collectors.toList())) .collect(Collectors.toList()))
.queue(bans -> { .queue(bans -> {
if (bans.size() > 1) if (bans.size() > 1) {
messages().GENERAL.ambiguousMember(channel).queue(); messages().getRegularMessage("general/error/ambiguous_member")
else if (bans.size() == 1) .apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
} else if (bans.size() == 1) {
tryUnban(channel, guild, performer, bans.get(0).getUser()); tryUnban(channel, guild, performer, bans.get(0).getUser());
}
}); });
} }
@ -77,7 +85,10 @@ public class UnbanCommand extends BaseCommand {
void realIdRun(CommandContext<MessageReceivedEvent> ctx) { void realIdRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return;
} }
final Guild guild = ctx.getSource().getGuild(); 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) { void tryUnban(MessageChannel channel, Guild guild, Member performer, User target) {
if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) if (!guild.getSelfMember().hasPermission(UNBAN_PERMISSION)) {
messages().GENERAL.insufficientPermissions(channel, UNBAN_PERMISSION).queue(); messages().getRegularMessage("general/error/insufficient_permissions")
else if (!performer.hasPermission(UNBAN_PERMISSION)) .apply(MessageHelper.member("performer", performer))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, UNBAN_PERMISSION).queue(); .with("required_permissions", UNBAN_PERMISSION::toString)
else .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) 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(); .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.Member;
import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand; import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage; import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal; import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
@ -45,34 +45,54 @@ public class UnwarnCommand extends BaseCommand {
void realRun(CommandContext<MessageReceivedEvent> ctx) { void realRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { 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; return;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
int caseID = IntegerArgumentType.getInteger(ctx, "caseId"); 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)) } else {
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else {
final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild); final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild);
@Nullable @Nullable
final WarningEntry entry = storage.getWarning(caseID); final WarningEntry entry = storage.getWarning(caseID);
Member temp; Member temp;
if (entry == null) if (entry == null) {
messages().MODERATION.ERRORS.noWarnWithID(channel, performer, caseID).queue(); messages().getRegularMessage("moderation/error/unwarn/no_case_found")
else if (entry.getWarned().getIdLong() == performer.getIdLong() .apply(MessageHelper.member("performer", performer))
&& !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) .with("case_id", () -> String.valueOf(caseID))
messages().MODERATION.ERRORS.cannotUnwarnSelf(channel, performer, caseID, entry).queue(); .send(getBot(), channel).queue();
else if (config().WARNINGS_RESPECT_MOD_ROLES.get()
&& (temp = guild.getMember(entry.getPerformer())) != null } else if (entry.getWarned().getIdLong() == performer.getIdLong()
&& !performer.canInteract(temp)) && !config().WARNINGS_REMOVE_SELF_WARNINGS.get()) {
messages().MODERATION.ERRORS.cannotRemoveHigherModerated(channel, performer, caseID, entry).queue(); messages().getRegularMessage("moderation/error/unwarn/cannot_unwarn_self")
else { .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); 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.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage; import sciwhiz12.janitor.moderation.warns.WarningStorage;
import sciwhiz12.janitor.msg.MessageHelper;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
@ -44,45 +45,70 @@ public class WarnCommand extends BaseCommand {
); );
} }
public int run(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException { int run(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException {
realRun(ctx, reason);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx, String reason) throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue(); messages().getRegularMessage("general/error/guild_only_command")
return; .apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild()); 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 Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC); final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target)) if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue(); messages().getRegularMessage("general/error/cannot_action_self")
else if (performer.equals(target)) .apply(MessageHelper.member("performer", performer))
messages().GENERAL.cannotActionPerformer(channel, performer).queue(); .send(getBot(), channel).queue();
else if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); } else if (performer.equals(target)) {
else if (!performer.canInteract(target)) messages().getRegularMessage("general/error/cannot_action_performer")
messages().MODERATION.ERRORS.cannotModerate(channel, performer, target).queue(); .apply(MessageHelper.member("performer", performer))
else if (target.hasPermission(WARN_PERMISSION) && config().WARNINGS_PREVENT_WARNING_MODS.get()) .send(getBot(), channel).queue();
messages().MODERATION.ERRORS.cannotWarnMods(channel, performer, target).queue();
else } 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() 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() .mapToResult()
.flatMap(res -> { .flatMap(res -> messages().getRegularMessage("moderation/warn/info")
int caseId = WarningStorage.get(getBot().getStorage(), guild) .apply(MessageHelper.member("performer", performer))
.addWarning(new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason)); .apply(MessageHelper.warningEntry("warning_entry", caseId, entry))
return messages().MODERATION .with("private_message", () -> res.isSuccess() ? "" : "")
.warnUser(channel, performer, target, reason, dateTime, caseId, res.isSuccess()); .send(getBot(), channel)
}) )
.queue(); .queue();
}
return 1;
} }
} }

View File

@ -1,6 +1,6 @@
package sciwhiz12.janitor.commands.moderation; 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.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -13,8 +13,9 @@ import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry; import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry; import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage; 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.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; 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.arguments.GuildMemberArgument.member;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument; import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal; 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 class WarnListCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS); 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)); .executes(ctx -> this.run(ctx, false, false));
} }
public int run(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator) 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)
throws CommandSyntaxException { throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel(); MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) { if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue(); messages().getRegularMessage("general/error/guild_only_command")
return; .apply(MessageHelper.user("performer", ctx.getSource().getAuthor()))
.send(getBot(), channel).queue();
return 1;
} }
final Guild guild = ctx.getSource().getGuild(); final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember()); final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
@ -73,31 +73,50 @@ public class WarnListCommand extends BaseCommand {
if (filterTarget) { if (filterTarget) {
final List<Member> members = getMembers("target", ctx).fromGuild(performer.getGuild()); 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); final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) { if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue(); messages().getRegularMessage("general/error/cannot_interact")
return; .apply(MessageHelper.member("target", target))
.send(getBot(), channel).queue();
return 1;
} }
predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong()); predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong());
} }
if (filterModerator) { if (filterModerator) {
final List<Member> members = getMembers("moderator", ctx).fromGuild(performer.getGuild()); 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); final Member mod = members.get(0);
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == mod.getIdLong()); 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)) } else {
messages().MODERATION.ERRORS.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue(); messages().<Map.Entry<Integer, WarningEntry>>getListingMessage("moderation/warn/list")
else .apply(MessageHelper.member("performer", performer))
messages().MODERATION.warnList(channel, WarningStorage.get(getBot().getStorage(), guild) .amountPerPage(8)
.getWarnings() .setEntryApplier((entry, subs) -> subs
.entrySet().stream() .with("warning_entry.case_id", () -> String.valueOf(entry.getKey()))
.filter(predicate) .apply(user("warning_entry.performer", entry.getValue().getPerformer()))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)) .apply(user("warning_entry.warned", entry.getValue().getWarned()))
).queue(); .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)
.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.Member;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneOffset; 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; import static sciwhiz12.janitor.utils.Util.nameFor;
public class ModerationHelper { public class ModerationHelper {
@ -18,7 +18,7 @@ public class ModerationHelper {
auditReason.append("Kicked by ") auditReason.append("Kicked by ")
.append(nameFor(performer.getUser())) .append(nameFor(performer.getUser()))
.append(" on ") .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) if (reason != null)
auditReason.append(" for reason: ").append(reason); auditReason.append(" for reason: ").append(reason);
return guild.kick(target, auditReason.toString()); return guild.kick(target, auditReason.toString());
@ -30,7 +30,7 @@ public class ModerationHelper {
auditReason.append("Banned by ") auditReason.append("Banned by ")
.append(nameFor(performer.getUser())) .append(nameFor(performer.getUser()))
.append(" on ") .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) if (reason != null)
auditReason.append(" for reason: ").append(reason); auditReason.append(" for reason: ").append(reason);
return guild.ban(target, deleteDuration, auditReason.toString()); 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.FileNotFoundAction;
import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.core.file.FileWatcher;
import com.electronwill.nightconfig.toml.TomlFormat; import com.electronwill.nightconfig.toml.TomlFormat;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable;
import static sciwhiz12.janitor.Logging.CONFIG; import static sciwhiz12.janitor.Logging.CONFIG;
import static sciwhiz12.janitor.Logging.JANITOR; import static sciwhiz12.janitor.Logging.JANITOR;
@ -23,6 +23,7 @@ public class BotConfig {
public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL; public final CommentedConfigSpec.IntValue AUTOSAVE_INTERVAL;
public final CommentedConfigSpec.ConfigValue<String> CUSTOM_TRANSLATION_FILE; public final CommentedConfigSpec.ConfigValue<String> CUSTOM_TRANSLATION_FILE;
public final CommentedConfigSpec.ConfigValue<String> CUSTOM_MESSAGES_DIRECTORY;
public final CommentedConfigSpec.ConfigValue<String> COMMAND_PREFIX; 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.", .comment("A file which contains custom translation keys to load for messages.",
"If blank, no file shall be loaded.") "If blank, no file shall be loaded.")
.define("messages.custom_translations", ""); .define("messages.custom_translations", "");
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 COMMAND_PREFIX = builder
.comment("The prefix for commands.") .comment("The prefix for commands.")
@ -115,8 +120,7 @@ public class BotConfig {
spec.setConfig(config); spec.setConfig(config);
// TODO: config spec // TODO: config spec
FileWatcher.defaultInstance().addWatch(configPath, this::onFileChange); FileWatcher.defaultInstance().addWatch(configPath, this::onFileChange);
} } catch (IOException ex) {
catch (IOException ex) {
JANITOR.error("Error while building config from file {}", configPath, ex); JANITOR.error("Error while building config from file {}", configPath, ex);
} }
} }
@ -134,6 +138,15 @@ public class BotConfig {
.orElse(null); .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() { public String getToken() {
return options.getToken().orElse(CLIENT_TOKEN.get()); return options.getToken().orElse(CLIENT_TOKEN.get());
} }
@ -157,8 +170,7 @@ public class BotConfig {
CONFIG.info("Reloading config due to file change {}", configPath); CONFIG.info("Reloading config due to file change {}", configPath);
config.load(); config.load();
spec.setConfig(config); spec.setConfig(config);
} } catch (Exception ex) {
catch (Exception ex) {
CONFIG.error("Error while reloading config from {}", configPath, 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 OptionSet options;
private final ArgumentAcceptingOptionSpec<Path> configPath; private final ArgumentAcceptingOptionSpec<Path> configPath;
private final ArgumentAcceptingOptionSpec<Path> translationsPath; private final ArgumentAcceptingOptionSpec<Path> translationsPath;
private final ArgumentAcceptingOptionSpec<Path> messagesFolder;
private final ArgumentAcceptingOptionSpec<String> token; private final ArgumentAcceptingOptionSpec<String> token;
private final ArgumentAcceptingOptionSpec<String> prefix; private final ArgumentAcceptingOptionSpec<String> prefix;
private final ArgumentAcceptingOptionSpec<Long> owner; private final ArgumentAcceptingOptionSpec<Long> owner;
@ -28,6 +29,10 @@ public class BotOptions {
.accepts("translations", "The path to the translations file") .accepts("translations", "The path to the translations file")
.withRequiredArg() .withRequiredArg()
.withValuesConvertedBy(new PathConverter(FILE_EXISTING, READABLE)); .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 this.token = parser
.accepts("token", "The Discord token for the bot user") .accepts("token", "The Discord token for the bot user")
.withRequiredArg(); .withRequiredArg();
@ -49,6 +54,10 @@ public class BotOptions {
return translationsPath.valueOptional(options); return translationsPath.valueOptional(options);
} }
public Optional<Path> getMessagesFolder() {
return messagesFolder.valueOptional(options);
}
public Optional<String> getToken() { public Optional<String> getToken() {
return token.valueOptional(options); return token.valueOptional(options);
} }

View File

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

View File

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

View File

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

View File

@ -1,18 +1,19 @@
package sciwhiz12.janitor.moderation.warns; package sciwhiz12.janitor.moderation.warns;
import com.google.gson.JsonDeserializationContext; import com.fasterxml.jackson.core.JsonGenerator;
import com.google.gson.JsonDeserializer; import com.fasterxml.jackson.core.JsonParser;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.DeserializationContext;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonParseException; import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.gson.JsonSerializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.google.gson.JsonSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import java.lang.reflect.Type; import java.io.IOException;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class WarningEntry { public class WarningEntry {
@ -62,35 +63,42 @@ public class WarningEntry {
return Objects.hash(getPerformer(), getWarned(), getDateTime(), getReason()); return Objects.hash(getPerformer(), getWarned(), getDateTime(), getReason());
} }
public static class Serializer implements JsonDeserializer<WarningEntry>, JsonSerializer<WarningEntry> { public static class Serializer extends StdSerializer<WarningEntry> {
private final JanitorBot bot; 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; this.bot = bot;
} }
@Override @Override
public WarningEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) public WarningEntry deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
throws JsonParseException { final JsonNode obj = ctx.readTree(p);
final JsonObject obj = json.getAsJsonObject(); User performer = bot.get().getDiscord().retrieveUserById(obj.get("performer").asLong()).complete();
final User warned = bot.getDiscord().retrieveUserById(obj.get("warned").getAsLong()).complete(); User warned = bot.get().getDiscord().retrieveUserById(obj.get("warned").asLong()).complete();
final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete(); OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").asText());
final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString()); String contents = obj.get("reason").asText();
@Nullable return new WarningEntry(performer, warned, dateTime, contents);
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;
} }
} }
} }

View File

@ -1,11 +1,11 @@
package sciwhiz12.janitor.moderation.warns; package sciwhiz12.janitor.moderation.warns;
import com.electronwill.nightconfig.core.utils.ObservedMap; import com.electronwill.nightconfig.core.utils.ObservedMap;
import com.google.gson.Gson; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.GsonBuilder; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.gson.reflect.TypeToken; import com.fasterxml.jackson.databind.node.ObjectNode;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
@ -13,28 +13,23 @@ import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage; import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey; import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class WarningStorage extends JsonStorage { 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 final StorageKey<WarningStorage> KEY = new StorageKey<>("warnings", WarningStorage.class);
public static WarningStorage get(GuildStorage storage, Guild guild) { public static WarningStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, KEY, () -> new WarningStorage(storage.getBot())); return storage.getOrCreate(guild, KEY, () -> new WarningStorage(storage.getBot()));
} }
private final Gson gson;
private final JanitorBot bot; private final JanitorBot bot;
private int lastID = 1; private int lastID = 1;
private final Map<Integer, WarningEntry> warnings = new ObservedMap<>(new HashMap<>(), this::markDirty); private final Map<Integer, WarningEntry> warnings = new ObservedMap<>(new HashMap<>(), this::markDirty);
public WarningStorage(JanitorBot bot) { public WarningStorage(JanitorBot bot) {
this.bot = bot; this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create();
} }
public JanitorBot getBot() { public JanitorBot getBot() {
@ -52,8 +47,8 @@ public class WarningStorage extends JsonStorage {
return warnings.get(caseID); return warnings.get(caseID);
} }
public WarningEntry removeWarning(int caseID) { public void removeWarning(int caseID) {
return warnings.remove(caseID); warnings.remove(caseID);
} }
public Map<Integer, WarningEntry> getWarnings() { public Map<Integer, WarningEntry> getWarnings() {
@ -61,18 +56,27 @@ public class WarningStorage extends JsonStorage {
} }
@Override @Override
public JsonElement save() { protected void initialize(ObjectMapper mapper) {
JsonObject obj = new JsonObject(); super.initialize(mapper);
obj.addProperty("lastCaseID", lastID); mapper.registerModule(
obj.add("warnings", gson.toJsonTree(warnings)); 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; return obj;
} }
@Override @Override
public void load(JsonElement in) { public void load(JsonNode in, ObjectMapper mapper) {
final JsonObject obj = in.getAsJsonObject(); lastID = in.get("lastCaseID").asInt();
lastID = obj.get("lastCaseID").getAsInt(); final Map<Integer, WarningEntry> loaded = mapper.convertValue(in.get("warnings"), WARNING_MAP_TYPE);
final Map<Integer, WarningEntry> loaded = gson.fromJson(obj.get("warnings"), WARNING_MAP_TYPE);
warnings.clear(); warnings.clear();
warnings.putAll(loaded); 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; 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.JanitorBot;
import sciwhiz12.janitor.msg.json.ListingMessage;
import sciwhiz12.janitor.msg.json.RegularMessage;
import java.time.OffsetDateTime; import java.io.IOException;
import java.time.ZoneOffset; 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 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; private final JanitorBot bot;
public final General GENERAL; private final Path messagesFolder;
public final Moderation MODERATION; 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.bot = bot;
this.GENERAL = new General(this); this.messagesFolder = messagesFolder;
this.MODERATION = new Moderation(this); loadMessages();
} }
public String translate(String key, Object... args) { public JanitorBot getBot() {
return bot.getTranslations().translate(key, args); return bot;
} }
public EmbedBuilder failureEmbed(String title) { public void loadMessages() {
return new EmbedBuilder() boolean success = false;
.setTitle(title)
.setColor(FAILURE_COLOR) if (messagesFolder != null) {
.setTimestamp(OffsetDateTime.now(ZoneOffset.UTC)); 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; 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.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.JANITOR;
import static sciwhiz12.janitor.Logging.TRANSLATIONS; import static sciwhiz12.janitor.Logging.TRANSLATIONS;
public class Translations { public class TranslationMap {
private static final Gson GSON = new GsonBuilder().create(); public static final Pattern TRANSLATION_REGEX = Pattern.compile("<(.+?)>", CASE_INSENSITIVE);
private static final String DEFAULT_TRANSLATIONS_RESOURCE = "english.json"; 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 JanitorBot bot;
private final Path translationsFile; private final Path translationsFile;
private final Map<String, String> translations = new HashMap<>(); 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.bot = bot;
this.translationsFile = translationsFile; this.translationsFile = translationsFile;
loadTranslations(); loadTranslations();
@ -40,12 +43,11 @@ public class Translations {
} }
try { try {
JANITOR.debug(TRANSLATIONS, "Loading translations from file {}", translationsFile); 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.clear();
translations.putAll(trans); translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} translations from file {}", translations.size(), translationsFile); 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); JANITOR.error(TRANSLATIONS, "Error while loading translations from file {}", translationsFile, e);
loadDefaultTranslations(); loadDefaultTranslations();
} }
@ -55,14 +57,13 @@ public class Translations {
try { try {
JANITOR.debug(TRANSLATIONS, "Loading default english translations"); JANITOR.debug(TRANSLATIONS, "Loading default english translations");
// noinspection UnstableApiUsage // noinspection UnstableApiUsage
Map<String, String> trans = GSON.fromJson( Map<String, String> trans = jsonMapper.readValue(
new InputStreamReader(Resources.getResource(DEFAULT_TRANSLATIONS_RESOURCE).openStream()), new InputStreamReader(Resources.getResource(DEFAULT_TRANSLATIONS_RESOURCE).openStream()),
MAP_TYPE); MAP_TYPE);
translations.clear(); translations.clear();
translations.putAll(trans); translations.putAll(trans);
JANITOR.info(TRANSLATIONS, "Loaded {} default english translations", translations.size()); 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); JANITOR.error(TRANSLATIONS, "Error while loading default english translations", e);
} }
} }
@ -71,7 +72,13 @@ public class Translations {
return Collections.unmodifiableMap(translations); return Collections.unmodifiableMap(translations);
} }
public String translate(String key, Object... args) { public String translate(String text) {
return String.format(translations.getOrDefault(key, key), args); 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; package sciwhiz12.janitor.storage;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.Logging; import sciwhiz12.janitor.Logging;
@ -22,8 +20,6 @@ import static java.nio.file.StandardOpenOption.*;
* A storage system for guild-specific data. * A storage system for guild-specific data.
*/ */
public class GuildStorage { public class GuildStorage {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final JanitorBot bot; private final JanitorBot bot;
private final Path mainFolder; private final Path mainFolder;
private final Map<Long, Map<String, InnerStorage<?>>> guildStorages = new HashMap<>(); private final Map<Long, Map<String, InnerStorage<?>>> guildStorages = new HashMap<>();
@ -45,7 +41,7 @@ public class GuildStorage {
public <S extends IStorage> S getOrCreate(long guildID, StorageKey<S> key, Supplier<S> defaultSupplier) { public <S extends IStorage> S getOrCreate(long guildID, StorageKey<S> key, Supplier<S> defaultSupplier) {
final Map<String, InnerStorage<?>> storageMappy = guildStorages.computeIfAbsent(guildID, id -> new HashMap<>()); final Map<String, InnerStorage<?>> storageMappy = guildStorages.computeIfAbsent(guildID, id -> new HashMap<>());
return key.getType().cast(storageMappy.computeIfAbsent(key.getStorageID(), return key.getType().cast(storageMappy.computeIfAbsent(key.getStorageID(),
k -> new InnerStorage<>(key, load(guildID, key.getStorageID(), defaultSupplier.get()))).getStorage()); k -> new InnerStorage<>(key, load(guildID, key.getStorageID(), defaultSupplier.get()))).getStorage());
} }
private Path getFile(long guildID, String key) { private Path getFile(long guildID, String key) {
@ -85,7 +81,7 @@ public class GuildStorage {
if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent()); if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent());
if (Files.notExists(file)) Files.createFile(file); if (Files.notExists(file)) Files.createFile(file);
try (Writer writer = Files try (Writer writer = Files
.newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) { .newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) {
inner.getStorage().write(writer); inner.getStorage().write(writer);
anySaved = true; anySaved = true;
} }

View File

@ -1,5 +1,6 @@
package sciwhiz12.janitor.storage; package sciwhiz12.janitor.storage;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
@ -7,7 +8,7 @@ public interface IStorage {
boolean dirty(); 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; package sciwhiz12.janitor.storage;
import com.google.gson.Gson; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.GsonBuilder; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonElement; import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
public abstract class JsonStorage extends AbstractStorage { public abstract class JsonStorage extends AbstractStorage {
public static final Gson GSON = new GsonBuilder() protected final ObjectMapper jsonMapper = new ObjectMapper()
.serializeNulls() .enable(SerializationFeature.INDENT_OUTPUT)
.setPrettyPrinting() .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
.create();
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 @Override
public void write(Writer input) { public void write(Writer input) throws IOException {
GSON.toJson(save(), input); jsonMapper.writeTree(jsonMapper.createGenerator(input), save(jsonMapper));
} }
@Override @Override
public void read(Reader input) { public void read(Reader input) throws IOException {
load(JsonParser.parseReader(input)); load(jsonMapper.readTree(input), jsonMapper);
} }
} }

View File

@ -57,7 +57,7 @@ public class StorageKey<S extends IStorage> {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
StorageKey<?> that = (StorageKey<?>) o; StorageKey<?> that = (StorageKey<?>) o;
return storageID.equals(that.storageID) && return storageID.equals(that.storageID) &&
type.equals(that.type); type.equals(that.type);
} }
@Override @Override

View File

@ -37,8 +37,7 @@ public class StringReaderUtil {
} }
private static final char SYNTAX_ESCAPE = '\\'; 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 { public static String readStringUntil(StringReader reader, char terminator) throws CommandSyntaxException {
final StringBuilder result = new StringBuilder(); final StringBuilder result = new StringBuilder();
boolean escaped = false; boolean escaped = false;
@ -50,7 +49,8 @@ public class StringReaderUtil {
escaped = false; escaped = false;
} else { } else {
reader.setCursor(reader.getCursor() - 1); 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) { } else if (c == SYNTAX_ESCAPE) {
escaped = true; escaped = true;

View File

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

View File

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