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

Add notes system and commands

This commit is contained in:
Arnold Alejo Nunag 2020-10-01 08:14:56 +08:00
parent 934fbeb2f4
commit 99ca3b101a
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
7 changed files with 494 additions and 2 deletions

View File

@ -15,6 +15,7 @@ import sciwhiz12.janitor.commands.misc.OKCommand;
import sciwhiz12.janitor.commands.misc.PingCommand;
import sciwhiz12.janitor.commands.moderation.BanCommand;
import sciwhiz12.janitor.commands.moderation.KickCommand;
import sciwhiz12.janitor.commands.moderation.NoteCommand;
import sciwhiz12.janitor.commands.moderation.UnbanCommand;
import sciwhiz12.janitor.commands.moderation.UnwarnCommand;
import sciwhiz12.janitor.commands.moderation.WarnCommand;
@ -49,6 +50,7 @@ public class CommandRegistry implements EventListener {
addCommand(new WarnListCommand(this));
addCommand(new UnwarnCommand(this));
addCommand(new ShutdownCommand(this));
addCommand(new NoteCommand(this));
}
public CommandDispatcher<MessageReceivedEvent> getDispatcher() {

View File

@ -0,0 +1,201 @@
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 org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.commands.BaseCommand;
import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.notes.NoteStorage;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
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.moderation.NoteCommand.ModeratorFilter.*;
import static sciwhiz12.janitor.commands.util.CommandHelper.argument;
import static sciwhiz12.janitor.commands.util.CommandHelper.literal;
public class NoteCommand extends BaseCommand {
public static EnumSet<Permission> NOTE_PERMISSION = EnumSet.of(Permission.KICK_MEMBERS);
public NoteCommand(CommandRegistry registry) {
super(registry);
}
@Override
public LiteralArgumentBuilder<MessageReceivedEvent> getNode() {
return literal("note")
.requires(ctx -> config().NOTES_ENABLE.get())
.then(literal("add")
.then(argument("target", member())
.then(argument("contents", greedyString())
.executes(ctx -> this.addNote(ctx, getString(ctx, "contents")))
)
)
)
.then(literal("list")
.then(literal("mod")
.then(argument("moderator", member())
.then(argument("target", member())
.executes(ctx -> this.listNotes(ctx, true, ARGUMENT))
)
.executes(ctx -> this.listNotes(ctx, false, ARGUMENT))
)
)
.then(literal("me")
.then(argument("target", member())
.executes(ctx -> this.listNotes(ctx, true, PERFORMER))
)
)
.then(argument("target", member())
.executes(ctx -> this.listNotes(ctx, true, NONE))
)
.executes(ctx -> this.listNotes(ctx, false, NONE))
)
.then(literal("remove")
.then(argument("noteId", integer(1))
.executes(ctx -> this.removeNote(ctx, getInteger(ctx, "noteId")))
)
)
.then(literal("me")
.then(argument("target", member())
.executes(ctx -> this.listNotes(ctx, true, PERFORMER))
)
.executes(ctx -> this.listNotes(ctx, false, PERFORMER))
)
.then(argument("target", member())
.executes(ctx -> this.listNotes(ctx, true, NONE))
.then(argument("contents", greedyString())
.executes(ctx -> this.addNote(ctx, getString(ctx, "contents")))
)
);
}
private int addNote(CommandContext<MessageReceivedEvent> ctx, String noteContents) throws CommandSyntaxException {
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(ctx.getSource().getChannel());
return 1;
}
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final Guild guild = performer.getGuild();
final MessageChannel channel = ctx.getSource().getChannel();
final List<Member> members = getMembers("target", ctx).fromGuild(guild);
if (members.size() < 1) return 1;
final Member target = members.get(0);
final OffsetDateTime dateTime = OffsetDateTime.now(ZoneOffset.UTC);
if (guild.getSelfMember().equals(target))
messages().GENERAL.cannotActionSelf(channel).queue();
else if (performer.equals(target))
messages().GENERAL.cannotActionPerformer(channel, performer).queue();
else if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
final int maxAmount = config().NOTES_MAX_AMOUNT_PER_MOD.get();
if (storage.getAmountOfNotes(target.getUser()) >= maxAmount) {
messages().MODERATION.maxAmountOfNotes(channel, performer, target, maxAmount).queue();
} else {
int noteID = storage.addNote(new NoteEntry(performer.getUser(), target.getUser(), dateTime, noteContents));
messages().MODERATION.addNote(channel, performer, target, noteContents, dateTime, noteID).queue();
}
}
return 1;
}
enum ModeratorFilter {
NONE, PERFORMER, ARGUMENT
}
private int listNotes(CommandContext<MessageReceivedEvent> ctx, boolean filterTarget, ModeratorFilter modFilter)
throws CommandSyntaxException {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
Predicate<Map.Entry<Integer, NoteEntry>> predicate = e -> true;
if (filterTarget) {
final List<Member> members = getMembers("target", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return 1;
final Member target = members.get(0);
if (guild.getSelfMember().equals(target)) {
messages().GENERAL.cannotActionSelf(channel).queue();
return 1;
}
predicate = predicate.and(e -> e.getValue().getTarget().getIdLong() == target.getIdLong());
}
switch (modFilter) {
case ARGUMENT: {
final List<Member> members = getMembers("moderator", ctx).fromGuild(performer.getGuild());
if (members.size() < 1) return 1;
final Member mod = members.get(0);
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == mod.getIdLong());
}
case PERFORMER: {
predicate = predicate.and(e -> e.getValue().getPerformer().getIdLong() == performer.getIdLong());
}
}
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else
messages().MODERATION.noteList(channel, NoteStorage.get(getBot().getStorage(), guild)
.getNotes()
.entrySet().stream()
.filter(predicate)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
).queue();
return 1;
}
private int removeNote(CommandContext<MessageReceivedEvent> ctx, int noteID) {
MessageChannel channel = ctx.getSource().getChannel();
if (!ctx.getSource().isFromGuild()) {
messages().GENERAL.guildOnlyCommand(channel).queue();
return 1;
}
final Guild guild = ctx.getSource().getGuild();
final Member performer = Objects.requireNonNull(ctx.getSource().getMember());
final OffsetDateTime dateTime = OffsetDateTime.now();
if (!performer.hasPermission(NOTE_PERMISSION))
messages().MODERATION.performerInsufficientPermissions(channel, performer, NOTE_PERMISSION).queue();
else {
final NoteStorage storage = NoteStorage.get(getBot().getStorage(), guild);
@Nullable
final NoteEntry entry = storage.getNote(noteID);
if (entry == null)
messages().MODERATION.noNoteFound(channel, performer, noteID).queue();
else {
storage.removeNote(noteID);
messages().MODERATION.removeNote(channel, performer, noteID, entry).queue();
}
}
return 1;
}
}

View File

@ -31,6 +31,9 @@ public class BotConfig {
public final CommentedConfigSpec.BooleanValue WARNINGS_PREVENT_WARNING_MODS;
public final CommentedConfigSpec.BooleanValue WARNINGS_REMOVE_SELF_WARNINGS;
public final CommentedConfigSpec.IntValue NOTES_MAX_AMOUNT_PER_MOD;
public final CommentedConfigSpec.BooleanValue NOTES_ENABLE;
private final BotOptions options;
private final Path configPath;
private final CommentedConfigSpec spec;
@ -87,6 +90,15 @@ public class BotConfig {
.comment("Whether to allow moderators to remove warnings from themselves.")
.define("remove_self_warnings", false);
builder.pop();
builder.comment("Settings for the notes system").push("notes");
NOTES_ENABLE = builder
.comment("Whether to enable the notes system. If disabled, the related commands are force-disabled.")
.define("enable", true);
NOTES_MAX_AMOUNT_PER_MOD = builder
.comment("The max amount of notes for a user per moderator.")
.defineInRange("max_amount", Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
builder.pop();
}
builder.pop();

View File

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

View File

@ -0,0 +1,85 @@
package sciwhiz12.janitor.moderation.notes;
import com.electronwill.nightconfig.core.utils.ObservedMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
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 NoteStorage extends JsonStorage {
private static final Type NOTE_MAP_TYPE = new TypeToken<Map<Integer, NoteEntry>>() {}.getType();
public static final String STORAGE_KEY = "notes";
public static NoteStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, STORAGE_KEY, () -> new NoteStorage(storage.getBot()));
}
private final Gson gson;
private final JanitorBot bot;
private int lastID = 1;
private final Map<Integer, NoteEntry> notes = new ObservedMap<>(new HashMap<>(), this::markDirty);
public NoteStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {
return bot;
}
public int addNote(NoteEntry entry) {
int id = lastID++;
notes.put(id, entry);
return id;
}
@Nullable
public NoteEntry getNote(int noteID) {
return notes.get(noteID);
}
public NoteEntry removeNote(int noteID) {
return notes.remove(noteID);
}
public int getAmountOfNotes(User target) {
return (int) notes.values().stream()
.filter(entry -> entry.getTarget() == target)
.count();
}
public Map<Integer, NoteEntry> getNotes() {
return notes;
}
@Override
public JsonElement save() {
JsonObject obj = new JsonObject();
obj.addProperty("lastNoteID", lastID);
obj.add("notes", gson.toJsonTree(notes));
return obj;
}
@Override
public void load(JsonElement in) {
final JsonObject obj = in.getAsJsonObject();
lastID = obj.get("lastNoteID").getAsInt();
final Map<Integer, NoteEntry> loaded = gson.fromJson(obj.get("notes"), NOTE_MAP_TYPE);
notes.clear();
notes.putAll(loaded);
}
}

View File

@ -11,6 +11,7 @@ import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.restaction.MessageAction;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.moderation.notes.NoteEntry;
import sciwhiz12.janitor.moderation.warns.WarningEntry;
import java.time.Clock;
@ -326,7 +327,8 @@ public class Messages {
return channel.sendMessage(embed.build());
}
public MessageAction cannotRemoveHigherModerated(MessageChannel channel, Member performer, int caseID, WarningEntry entry) {
public MessageAction cannotRemoveHigherModerated(MessageChannel channel, Member performer, int caseID,
WarningEntry entry) {
final EmbedBuilder embed = new EmbedBuilder()
.setTitle(translate("moderation.unwarn.cannot_remove_higher_mod.title"), null)
.setColor(General.FAILURE_COLOR)
@ -339,6 +341,81 @@ public class Messages {
.addField(translate("moderation.unwarn.cannot_remove_higher_mod.field.case_id"), String.valueOf(caseID), true);
return channel.sendMessage(embed.build());
}
public MessageAction maxAmountOfNotes(MessageChannel channel, Member performer, Member target, int amount) {
final EmbedBuilder embed = new EmbedBuilder()
.setTitle(translate("moderation.note.max_amount_of_notes.title"), null)
.setColor(General.FAILURE_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.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);
return channel.sendMessage(embed.build());
}
public MessageAction noNoteFound(MessageChannel channel, Member performer, int noteID) {
final EmbedBuilder embed = new EmbedBuilder()
.setTitle(translate("moderation.note.no_note_found.title"), null)
.setColor(General.FAILURE_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.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);
return channel.sendMessage(embed.build());
}
public MessageAction addNote(MessageChannel channel, Member performer, Member target, String contents,
OffsetDateTime dateTime, int noteID) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.note.add.author"), null, GAVEL_ICON_URL)
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.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);
return channel.sendMessage(embed.build());
}
public MessageAction noteList(MessageChannel channel, Map<Integer, NoteEntry> displayNotes) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.note.list.author"), null, GAVEL_ICON_URL)
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()));
String warningsDesc = 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");
embed.setDescription(warningsDesc);
return channel.sendMessage(embed.build());
}
public MessageAction removeNote(MessageChannel channel, Member performer, int noteID, NoteEntry entry) {
final EmbedBuilder embed = new EmbedBuilder()
.setAuthor(translate("moderation.note.remove.author"), null, GAVEL_ICON_URL)
.setColor(MODERATION_COLOR)
.setTimestamp(OffsetDateTime.now(Clock.systemUTC()))
.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);
return channel.sendMessage(embed.build());
}
}
}

View File

@ -81,5 +81,30 @@
"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.field.performer": "Performer",
"moderation.warn.cannot_remove_higher_mod.field.original_performer": "Original Performer",
"moderation.warn.cannot_remove_higher_mod.field.case_id": "Case ID"
"moderation.warn.cannot_remove_higher_mod.field.case_id": "Case ID",
"moderation.note.max_amount_of_notes.title": "Max notes reached.",
"moderation.note.max_amount_of_notes.desc": "The performer has reached the maximum amount of notes for the target user.",
"moderation.note.max_amount_of_notes.field.performer": "Performer",
"moderation.note.max_amount_of_notes.field.target": "Target",
"moderation.note.max_amount_of_notes.field.amount": "(Max.) Amount",
"moderation.note.no_note_found.title": "No note found.",
"moderation.note.no_note_found.desc": "No note with that note ID was found.",
"moderation.note.no_note_found.field.performer": "Performer",
"moderation.note.no_note_found.field.note_id": "Note ID",
"moderation.note.list.author": "Listing of Notes",
"moderation.note.list.empty": "**_No recorded notes matching your query._**",
"moderation.note.list.entry": "**#%1$s**: for %2$s by %3$s %n - _Date & Time:_ %4$s %n - _Text:_ %5$s",
"moderation.note.add.author": "Recorded note for user.",
"moderation.note.add.field.performer": "Performer",
"moderation.note.add.field.target": "Target",
"moderation.note.add.field.note_id": "Note ID",
"moderation.note.add.field.date_time": "Date & Time",
"moderation.note.add.field.contents": "Text",
"moderation.note.remove.author": "Removed note.",
"moderation.note.remove.field.performer": "Performer",
"moderation.note.remove.field.original_target": "Original Target",
"moderation.note.remove.field.original_performer": "Original Performer",
"moderation.note.remove.field.note_id": "Note ID",
"moderation.note.remove.field.date_time": "Date & Time",
"moderation.note.remove.field.contents": "Text"
}