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

Add warnings system and commands, and per-guild storage

This commit is contained in:
Arnold Alejo Nunag 2020-10-01 00:53:08 +08:00
parent 8f9601a9c7
commit 412c499cfc
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
15 changed files with 773 additions and 2 deletions

View File

@ -0,0 +1,120 @@
package sciwhiz12.janitor;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.dv8tion.jda.api.entities.Guild;
import sciwhiz12.janitor.storage.IStorage;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Supplier;
import static java.nio.file.StandardOpenOption.*;
public class GuildStorage {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final JanitorBot bot;
private final Path mainFolder;
private final Map<Guild, Map<String, IStorage>> guildStorage = new IdentityHashMap<>();
public GuildStorage(JanitorBot bot, Path mainFolder) {
Preconditions.checkArgument(Files.isDirectory(mainFolder) || Files.notExists(mainFolder));
this.bot = bot;
this.mainFolder = mainFolder;
}
public JanitorBot getBot() {
return bot;
}
public <T extends IStorage> T getOrCreate(Guild guild, String key, Supplier<T> defaultSupplier) {
final Map<String, IStorage> storageMap = guildStorage.computeIfAbsent(guild, g -> new HashMap<>());
//noinspection unchecked
return (T) storageMap.computeIfAbsent(key, k -> load(guild, key, defaultSupplier.get()));
}
private Path getFile(Guild guild, String key) {
final Path guildFolder = makeFolder(guild);
final Path file = Path.of(key + ".json");
return mainFolder.resolve(guildFolder).resolve(file);
}
public <T extends IStorage> T load(Guild guild, String key, T storage) {
final Path file = getFile(guild, key);
if (Files.notExists(file)) return storage;
Logging.JANITOR.debug("Loading storage {} for guild {}", key, guild);
try (Reader reader = Files.newBufferedReader(file)) {
storage.read(reader);
}
catch (IOException e) {
Logging.JANITOR.error("Error while loading storage {} for guild {}", key, guild, e);
}
return storage;
}
public void save() {
Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder);
boolean anySaved = false;
for (Guild guild : guildStorage.keySet()) {
final Map<String, IStorage> storageMap = guildStorage.get(guild);
for (String key : storageMap.keySet()) {
final IStorage storage = storageMap.get(key);
if (storage.dirty()) {
final Path file = getFile(guild, key);
try {
if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent());
if (Files.notExists(file)) Files.createFile(file);
try (Writer writer = Files
.newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) {
storage.write(writer);
anySaved = true;
}
}
catch (IOException e) {
Logging.JANITOR.error("Error while writing storage {} for guild {}", key, guild, e);
}
}
}
}
if (anySaved)
Logging.JANITOR.info("Saved guild storage to files under {}", mainFolder);
}
private Path makeFolder(Guild guild) {
return Path.of(Long.toHexString(guild.getIdLong()));
}
public static class SavingThread extends Thread {
private final GuildStorage storage;
private volatile boolean running = true;
public SavingThread(GuildStorage storage) {
this.storage = storage;
this.setName("GuildStorage-Saving-Thread");
this.setDaemon(true);
}
public void stopThread() {
running = false;
this.interrupt();
}
@Override
public void run() {
while (running) {
storage.save();
try { Thread.sleep(10000); }
catch (InterruptedException ignored) {}
}
}
}
}

View File

@ -22,12 +22,15 @@ public class JanitorBot {
private final BotConfig config; private final BotConfig config;
private final Messages messages; private final Messages messages;
private BotConsole console; private BotConsole console;
private final GuildStorage storage;
private final GuildStorage.SavingThread storageSavingThread;
private CommandRegistry cmdRegistry; private CommandRegistry cmdRegistry;
private Translations translations; private Translations translations;
public JanitorBot(JDA discord, BotConfig config) { public JanitorBot(JDA discord, BotConfig config) {
this.config = config; this.config = config;
this.console = new BotConsole(this, System.in); this.console = new BotConsole(this, System.in);
this.storage = new GuildStorage(this, config.getStoragePath());
this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix()); this.cmdRegistry = new CommandRegistry(this, config.getCommandPrefix());
this.discord = discord; this.discord = discord;
this.translations = new Translations(this, config.getTranslationsFile()); this.translations = new Translations(this, config.getTranslationsFile());
@ -47,6 +50,8 @@ public class JanitorBot {
error -> JANITOR.error(STATUS, "Error while sending ready message to owner", error) error -> JANITOR.error(STATUS, "Error while sending ready message to owner", error)
) )
); );
storageSavingThread = new GuildStorage.SavingThread(storage);
storageSavingThread.start();
console.start(); console.start();
} }
@ -60,6 +65,8 @@ public class JanitorBot {
public Messages getMessages() { return this.messages; } public Messages getMessages() { return this.messages; }
public GuildStorage getStorage() { return this.storage; }
public CommandRegistry getCommandRegistry() { public CommandRegistry getCommandRegistry() {
return this.cmdRegistry; return this.cmdRegistry;
} }
@ -70,7 +77,6 @@ public class JanitorBot {
public void shutdown() { public void shutdown() {
JANITOR.info(STATUS, "Shutting down!"); JANITOR.info(STATUS, "Shutting down!");
console.stop();
getConfig().getOwnerID() getConfig().getOwnerID()
.map(discord::retrieveUserById) .map(discord::retrieveUserById)
.map(owner -> .map(owner ->
@ -90,5 +96,8 @@ public class JanitorBot {
)) ))
).ifPresent(CompletableFuture::join); ).ifPresent(CompletableFuture::join);
discord.shutdown(); discord.shutdown();
storageSavingThread.stopThread();
storage.save();
console.stop();
} }
} }

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 STORAGE = MarkerFactory.getMarker("STORAGE");
public static final Logger JANITOR = LoggerFactory.getLogger("janitor"); public static final Logger JANITOR = LoggerFactory.getLogger("janitor");
public static final Logger CONSOLE = LoggerFactory.getLogger("janitor.console"); public static final Logger CONSOLE = LoggerFactory.getLogger("janitor.console");

View File

@ -16,6 +16,9 @@ import sciwhiz12.janitor.commands.misc.PingCommand;
import sciwhiz12.janitor.commands.moderation.BanCommand; import sciwhiz12.janitor.commands.moderation.BanCommand;
import sciwhiz12.janitor.commands.moderation.KickCommand; import sciwhiz12.janitor.commands.moderation.KickCommand;
import sciwhiz12.janitor.commands.moderation.UnbanCommand; import sciwhiz12.janitor.commands.moderation.UnbanCommand;
import sciwhiz12.janitor.commands.moderation.UnwarnCommand;
import sciwhiz12.janitor.commands.moderation.WarnCommand;
import sciwhiz12.janitor.commands.moderation.WarnListCommand;
import sciwhiz12.janitor.utils.Util; import sciwhiz12.janitor.utils.Util;
import java.util.HashMap; import java.util.HashMap;
@ -42,6 +45,9 @@ public class CommandRegistry implements EventListener {
addCommand(new KickCommand(this)); addCommand(new KickCommand(this));
addCommand(new BanCommand(this)); addCommand(new BanCommand(this));
addCommand(new UnbanCommand(this)); addCommand(new UnbanCommand(this));
addCommand(new WarnCommand(this));
addCommand(new WarnListCommand(this));
addCommand(new UnwarnCommand(this));
if (bot.getConfig().getOwnerID().isPresent()) { if (bot.getConfig().getOwnerID().isPresent()) {
addCommand(new ShutdownCommand(this, bot.getConfig().getOwnerID().get())); addCommand(new ShutdownCommand(this, bot.getConfig().getOwnerID().get()));
} }

View File

@ -0,0 +1,72 @@
package sciwhiz12.janitor.commands.moderation;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import java.time.OffsetDateTime;
import java.util.EnumSet;
import java.util.Objects;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
public class UnwarnCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
public UnwarnCommand(CommandRegistry registry) {
super(registry);
}
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("unwarn")
.then(argument("caseId", IntegerArgumentType.integer(1))
.executes(this::run)
);
}
public int run(CommandContext<MessageReceivedEvent> ctx) {
realRun(ctx);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
int caseID = IntegerArgumentType.getInteger(ctx, "caseId");
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else {
final WarningStorage storage = WarningStorage.get(getBot().getStorage(), guild);
@Nullable
final WarningEntry entry = storage.getWarning(caseID);
if (entry == null)
messages().MODERATION.noWarnWithID(channel, performer, caseID).queue();
else if (entry.getWarned().getIdLong() == performer.getIdLong())
messages().MODERATION.cannotUnwarnSelf(channel, performer, caseID, entry).queue();
else {
storage.removeWarning(caseID);
messages().MODERATION.unwarn(channel, performer, caseID, entry).queue();
}
}
}
}

View File

@ -0,0 +1,85 @@
package sciwhiz12.janitor.commands.moderation;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.getMembers;
import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
public class WarnCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
public WarnCommand(CommandRegistry registry) {
super(registry);
}
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warn")
.then(argument("member", member())
.then(argument("reason", greedyString())
.executes(ctx -> this.run(ctx, getString(ctx, "reason")))
)
);
}
public 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();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final List<Member> members = getMembers("member", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else if (!performer.canInteract(target))
messages().MODERATION.cannotModerate(channel, performer, target).queue();
else
target.getUser().openPrivateChannel()
.flatMap(dm -> messages().MODERATION.warnDM(dm, performer, target, reason, dateTime))
.mapToResult()
.flatMap(res -> {
int caseId = WarningStorage.get(getBot().getStorage(), guild)
.addWarning(new WarningEntry(target.getUser(), performer.getUser(), dateTime, reason));
return messages().MODERATION
.warnUser(channel, performer, target, reason, dateTime, caseId, res.isSuccess());
})
.queue();
}
}

View File

@ -0,0 +1,102 @@
package sciwhiz12.janitor.commands.moderation;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import sciwhiz12.janitor.moderation.warns.WarningStorage;
import java.time.OffsetDateTime;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.getMembers;
import static sciwhiz12.janitor.commands.arguments.GuildMemberArgument.member;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
public class WarnListCommand extends BaseCommand {
public static final EnumSet<Permission> WARN_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
public WarnListCommand(CommandRegistry registry) {
super(registry);
}
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("warnlist")
.then(literal("target")
.then(argument("target", member())
.then(literal("mod")
.then(argument("moderator", member())
.executes(ctx -> this.run(ctx, true, true))
)
)
.executes(ctx -> this.run(ctx, true, false))
)
).then(literal("mod")
.then(argument("moderator", member())
.executes(ctx -> this.run(ctx, false, true))
)
)
.executes(ctx -> this.run(ctx, false, false));
}
public int run(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator)
throws CommandSyntaxException {
realRun(ctx, filterTarget, filterModerator);
return 1;
}
void realRun(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, boolean filterModerator)
throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
Predicate<Map.Entry<Integer, WarningEntry>> predicate = e -> true;
if (filterTarget) {
final List<Member> members = getMembers("target", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue();
return;
}
predicate = predicate.and(e -> e.getValue().getWarned().getIdLong() == target.getIdLong());
}
if (filterModerator) {
final List<Member> members = getMembers("moderator", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return;
final Member mod = members.get(0);
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == mod.getIdLong());
}
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(WARN_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, WARN_PERMISSION).queue();
else
messages().MODERATION.warnList(channel, WarningStorage.get(getBot().getStorage(), guild)
.getWarnings()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).queue();
}
}

View File

@ -14,8 +14,10 @@ import static sciwhiz12.janitor.Logging.JANITOR;
public class BotConfig { public class BotConfig {
public static final Path DEFAULT_CONFIG_PATH = Path.of("config.toml"); public static final Path DEFAULT_CONFIG_PATH = Path.of("config.toml");
public static final Path DEFAULT_STORAGE_PATH = Path.of("guild_storage");
public static final String CLIENT_TOKEN = "discord.client_token"; public static final String CLIENT_TOKEN = "discord.client_token";
public static final String OWNER_ID = "discord.owner_id"; public static final String OWNER_ID = "discord.owner_id";
public static final String STORAGE_PATH = "storage.main_path";
public static final String TRANSLATION_FILE_PATH = "messages.translation_file"; public static final String TRANSLATION_FILE_PATH = "messages.translation_file";
public static final String COMMAND_PREFIX = "commands.prefix"; public static final String COMMAND_PREFIX = "commands.prefix";
@ -52,6 +54,10 @@ public class BotConfig {
.orElse(null); .orElse(null);
} }
public Path getStoragePath() {
return config.<String>getOptional(STORAGE_PATH).map(Path::of).orElse(DEFAULT_STORAGE_PATH);
}
public Optional<String> getToken() { public Optional<String> getToken() {
return options.getToken().or(() -> config.getOptional(CLIENT_TOKEN)); return options.getToken().or(() -> config.getOptional(CLIENT_TOKEN));
} }

View File

@ -0,0 +1,96 @@
package sciwhiz12.janitor.moderation.warns;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.dv8tion.jda.api.entities.User;
import sciwhiz12.janitor.JanitorBot;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import java.util.Objects;
import javax.annotation.Nullable;
public class WarningEntry {
private final User performer;
private final User warned;
private final OffsetDateTime dateTime;
@Nullable
private final String reason;
public WarningEntry(User warned, User performer, OffsetDateTime dateTime, @Nullable String reason) {
this.performer = performer;
this.warned = warned;
this.dateTime = dateTime;
this.reason = reason;
}
public User getPerformer() {
return performer;
}
public User getWarned() {
return warned;
}
public OffsetDateTime getDateTime() {
return dateTime;
}
@Nullable
public String getReason() {
return reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WarningEntry that = (WarningEntry) o;
return getPerformer().equals(that.getPerformer()) &&
getWarned().equals(that.getWarned()) &&
getDateTime().equals(that.getDateTime()) &&
Objects.equals(getReason(), that.getReason());
}
@Override
public int hashCode() {
return Objects.hash(getPerformer(), getWarned(), getDateTime(), getReason());
}
public static class Serializer implements JsonDeserializer<WarningEntry>, JsonSerializer<WarningEntry> {
private final JanitorBot bot;
public Serializer(JanitorBot bot) {
this.bot = bot;
}
@Override
public WarningEntry deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
final JsonObject obj = json.getAsJsonObject();
final User warned = bot.getDiscord().retrieveUserById(obj.get("warned").getAsLong()).complete();
final User performer = bot.getDiscord().retrieveUserById(obj.get("performer").getAsLong()).complete();
final OffsetDateTime dateTime = OffsetDateTime.parse(obj.get("dateTime").getAsString());
@Nullable
final String reason = obj.has("reason") ? obj.get("reason").getAsString() : null;
return new WarningEntry(warned, performer, dateTime, reason);
}
@Override
public JsonElement serialize(WarningEntry src, Type typeOfSrc, JsonSerializationContext context) {
final JsonObject obj = new JsonObject();
obj.addProperty("warned", src.getWarned().getId());
obj.addProperty("performer", src.getPerformer().getId());
obj.addProperty("dateTime", src.getDateTime().toString());
if (src.getReason() != null) {
obj.addProperty("reason", src.getReason());
}
return obj;
}
}
}

View File

@ -0,0 +1,78 @@
package sciwhiz12.janitor.moderation.warns;
import com.electronwill.nightconfig.core.utils.ObservedMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import net.dv8tion.jda.api.entities.Guild;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.GuildStorage;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.storage.JsonStorage;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class WarningStorage extends JsonStorage {
private static final Type WARNING_MAP_TYPE = new TypeToken<Map<Integer, WarningEntry>>() {}.getType();
public static final String STORAGE_KEY = "warnings";
public static WarningStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, STORAGE_KEY, () -> new WarningStorage(storage.getBot()));
}
private final Gson gson;
private final JanitorBot bot;
private int lastID = 1;
private final Map<Integer, WarningEntry> warnings = new ObservedMap<>(new HashMap<>(), this::markDirty);
public WarningStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {
return bot;
}
public int addWarning(WarningEntry entry) {
int id = lastID++;
warnings.put(id, entry);
return id;
}
@Nullable
public WarningEntry getWarning(int caseID) {
return warnings.get(caseID);
}
public WarningEntry removeWarning(int caseID) {
return warnings.remove(caseID);
}
public Map<Integer, WarningEntry> getWarnings() {
return warnings;
}
@Override
public JsonElement save() {
JsonObject obj = new JsonObject();
obj.addProperty("lastCaseID", lastID);
obj.add("warnings", gson.toJsonTree(warnings));
return obj;
}
@Override
public void load(JsonElement in) {
final JsonObject obj = in.getAsJsonObject();
lastID = obj.get("lastCaseID").getAsInt();
final Map<Integer, WarningEntry> loaded = gson.fromJson(obj.get("warnings"), WARNING_MAP_TYPE);
warnings.clear();
warnings.putAll(loaded);
}
}

View File

@ -11,12 +11,18 @@ import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.MessageAction; import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import java.time.Clock; import java.time.Clock;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
public class Messages { public class Messages {
private final JanitorBot bot; private final JanitorBot bot;
public final General GENERAL; public final General GENERAL;
@ -212,6 +218,100 @@ public class Messages {
.build() .build()
); );
} }
public MessageAction warnUser(MessageChannel channel, Member performer, Member target, String reason,
OffsetDateTime dateTime, int caseID, boolean sentDM) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.warn.info.author"), null, GAVEL_ICON_URL)
.addField(translate("moderation.warn.info.field.performer"), performer.getUser().getAsMention(), true)
.addField(translate("moderation.warn.info.field.target"), target.getUser().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);
return channel
.sendMessage(embed.setColor(MODERATION_COLOR).setTimestamp(OffsetDateTime.now(Clock.systemUTC())).build());
}
public MessageAction warnDM(MessageChannel channel, Member performer, Member target, String reason,
OffsetDateTime dateTime) {
final EmbedBuilder embed = new EmbedBuilder()
.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);
return channel.sendMessage(embed.build());
}
public MessageAction warnList(MessageChannel channel, Map<Integer, WarningEntry> displayWarnings) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.warnlist.author"), null, GAVEL_ICON_URL)
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()));
String warningsDesc = 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");
embed.setDescription(warningsDesc);
return channel.sendMessage(embed.build());
}
public MessageAction noWarnWithID(MessageChannel channel, Member performer, int caseID) {
final EmbedBuilder embed = new EmbedBuilder()
.setTitle(translate("moderation.unwarn.no_case_found.title"), null)
.setColor(General.FAILURE_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.setDescription(translate("moderation.unwarn.no_case_found.desc"))
.addField(translate("moderation.unwarn.no_case_found.field.performer"), performer.getUser().getAsMention(),
true)
.addField(translate("moderation.unwarn.no_case_found.field.case_id"), String.valueOf(caseID), true);
return channel.sendMessage(embed.build());
}
public MessageAction cannotUnwarnSelf(MessageChannel channel, Member performer, int caseID, WarningEntry entry) {
final EmbedBuilder embed = new EmbedBuilder()
.setTitle(translate("moderation.unwarn.cannot_unwarn_self.title"), null)
.setColor(General.FAILURE_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.setDescription(translate("moderation.unwarn.cannot_unwarn_self.desc"))
.addField(translate("moderation.unwarn.cannot_unwarn_self.field.performer"),
performer.getUser().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);
return channel.sendMessage(embed.build());
}
public MessageAction unwarn(MessageChannel channel, Member performer, int caseID, WarningEntry entry) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.unwarn.author"), null, GAVEL_ICON_URL)
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.addField(translate("moderation.unwarn.field.performer"), performer.getUser().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);
if (entry.getReason() != null)
embed.addField(translate("moderation.unwarn.field.reason"), entry.getReason(), false);
return channel.sendMessage(embed.build());
}
} }
} }

View File

@ -0,0 +1,22 @@
package sciwhiz12.janitor.storage;
public abstract class AbstractStorage implements IStorage {
private boolean dirty;
public boolean isDirty() {
return dirty;
}
@Override
public boolean dirty() {
if (dirty) {
dirty = false;
return true;
}
return false;
}
public void markDirty() {
this.dirty = true;
}
}

View File

@ -0,0 +1,13 @@
package sciwhiz12.janitor.storage;
import java.io.Reader;
import java.io.Writer;
public interface IStorage {
boolean dirty();
void write(Writer output);
void read(Reader input);
}

View File

@ -0,0 +1,30 @@
package sciwhiz12.janitor.storage;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import java.io.Reader;
import java.io.Writer;
public abstract class JsonStorage extends AbstractStorage {
public static final Gson GSON = new GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.create();
public abstract JsonElement save();
public abstract void load(JsonElement object);
@Override
public void write(Writer input) {
GSON.toJson(save(), input);
}
@Override
public void read(Reader input) {
load(JsonParser.parseReader(input));
}
}

View File

@ -41,5 +41,36 @@
"moderation.ban.dm.field.reason": "Reason", "moderation.ban.dm.field.reason": "Reason",
"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.field.performer": "Performer",
"moderation.warn.info.field.target": "Target",
"moderation.warn.info.field.sent_private_message": "Sent DM",
"moderation.warn.info.field.case_id": "Case ID",
"moderation.warn.info.field.date_time": "Date & Time",
"moderation.warn.info.field.reason": "Reason",
"moderation.warn.dm.title": "You were warned by a moderator.",
"moderation.warn.dm.field.performer": "Moderator",
"moderation.warn.dm.field.date_time": "Date & Time",
"moderation.warn.dm.field.reason": "Reason",
"moderation.warnlist.author": "Listing of Warnings",
"moderation.warnlist.empty": "**_No warnings logged matching your query._**",
"moderation.warnlist.entry": "**Case #%1$s**: Warned %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Reason:_ %5$s",
"moderation.warnlist.entry.no_reason": "_no reason specified_",
"moderation.unwarn.author": "Removed warning from user.",
"moderation.unwarn.field.performer": "Performer",
"moderation.unwarn.field.original_target": "Original Target",
"moderation.unwarn.field.original_performer": "Original Performer",
"moderation.unwarn.field.case_id": "Case ID",
"moderation.unwarn.field.date_time": "Date & Time",
"moderation.unwarn.field.reason": "Reason",
"moderation.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.field.performer": "Performer",
"moderation.unwarn.no_case_found.field.case_id": "Case ID",
"moderation.unwarn.cannot_unwarn_self.title": "Cannot remove warning from self.",
"moderation.unwarn.cannot_unwarn_self.desc": "Performer cannot remove a warning from themselves.",
"moderation.unwarn.cannot_unwarn_self.field.performer": "Performer/Original Target",
"moderation.unwarn.cannot_unwarn_self.field.original_performer": "Original Performer",
"moderation.unwarn.cannot_unwarn_self.field.case_id": "Case ID"
} }