Improve the way commutations, appeals, and clears work.

Specifically, commutations now create a new infraction,
and all three set a repeal reason/issuer/type.

This makes the system more flexible and makes for a better audit log.
master
James T. Martin 2020-11-25 11:18:09 -08:00
parent ea101d5c07
commit 3570b7ddab
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
24 changed files with 448 additions and 244 deletions

View File

@ -31,9 +31,9 @@ This plugin provides tools which both help moderators track what rule infraction
and help server administrators hold moderators accountable through an audit log. and help server administrators hold moderators accountable through an audit log.
This plugin provides these basic commands: This plugin provides these basic commands:
* `/ban` and `/unban` * `/ban`
* `/kick` * `/kick`
* `/mute` and `/unmute` * `/mute`
* `/warn` * `/warn`
All bans, kicks, mutes, and warnings are logged, All bans, kicks, mutes, and warnings are logged,
@ -43,21 +43,24 @@ and to help server administrators hold moderators accountable through an audit l
It is also possible to set the maximum length of bans or mutes that a moderator can issue It is also possible to set the maximum length of bans or mutes that a moderator can issue
in the configuration file. in the configuration file.
You may view the currently active bans and mutes using `/bans` and `/mutes` respectively, You may view the currently active sentences against any player,
or see the history of infractions a player has committed using `/infractions`. the infractions that a moderator has issued,
or see the history of infractions a player has committed using `/infractions query`.
This command does not include infractions that have been cleared from a player's record,
or old versions of infractions from before they were commuted.
You can remove an infraction from a player's account using commands like `/unwarn`. You can commute, extend, or update a sentence using `/infractions commute`,
Removing an infraction from a player's account will not remove the infraction from the audit log, remove and clear an infraction from a player's record using `/infractions clear`
and in fact will just add that the infraction was removed to the audit log. or remove a sentence without clearing the infraction from the player's record using `/infractions appeal`.
These commands do not remove the infraction from the audit log, and in fact create a new entry in the audit log.
You may view the audit log using `/auditlog`. You may view the audit log using `/auditlog`.
The audit log includes infractions that have been cleared, appealed, or commuted,
the history of those actions.
For more information on how to use these commands please see the in game `/help` or the `plugin.yml` For more information on how to use these commands please see the in game `/help` or the `plugin.yml`
For more information on how to configure this plugin, please see the default `config.yml`. For more information on how to configure this plugin, please see the default `config.yml`.
### Infraction histories & audit logs
A set of moderator tools which logs all moderator actions,
### Manuals ### Manuals
There are two built-in manuals: `/rules` and `/faq`. There are two built-in manuals: `/rules` and `/faq`.
These manuals provide both brief overviews and more detail which players may view if they wish. These manuals provide both brief overviews and more detail which players may view if they wish.
@ -66,12 +69,3 @@ It is also possible for moderators to (forcibly) show players portions of the ru
as a brief reminder of the rules or to quickly answer a question they were asking. as a brief reminder of the rules or to quickly answer a question they were asking.
Please see the in-game `/help` menu or `plugin.yml` for more information on how to use these commands. Please see the in-game `/help` menu or `plugin.yml` for more information on how to use these commands.
## Current Features
* Tracks how many zombies or husks a player kills.
* Supports a ranking system for players based on the U.S. Marines' ranks.
## Planned Features
* Make zombies horde more intelligently.
* Special world effects.
* A lot of stuff, generally, but it's been a few months so I don't remember it all. I'll add it here when work begins again.

View File

@ -4,17 +4,13 @@ import org.bukkit.plugin.java.JavaPlugin;
import me.jamestmartin.wasteland.manual.ManualState; import me.jamestmartin.wasteland.manual.ManualState;
import me.jamestmartin.wasteland.modtools.commands.CommandBan; import me.jamestmartin.wasteland.modtools.commands.CommandBan;
import me.jamestmartin.wasteland.modtools.commands.CommandBans;
import me.jamestmartin.wasteland.modtools.commands.CommandInfractions; import me.jamestmartin.wasteland.modtools.commands.CommandInfractions;
import me.jamestmartin.wasteland.modtools.commands.CommandKick; import me.jamestmartin.wasteland.modtools.commands.CommandKick;
import me.jamestmartin.wasteland.modtools.commands.CommandMute; import me.jamestmartin.wasteland.modtools.commands.CommandMute;
import me.jamestmartin.wasteland.modtools.commands.CommandMutes;
import me.jamestmartin.wasteland.modtools.commands.CommandUnban;
import me.jamestmartin.wasteland.modtools.commands.CommandUnmute;
import me.jamestmartin.wasteland.modtools.commands.CommandWarn; import me.jamestmartin.wasteland.modtools.commands.CommandWarn;
import me.jamestmartin.wasteland.modtools.config.ModToolsConfig; import me.jamestmartin.wasteland.modtools.config.ModToolsConfig;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.log.CommandModLog; import me.jamestmartin.wasteland.modtools.log.CommandAuditLog;
import me.jamestmartin.wasteland.offlineplayer.CommandLastSeen; import me.jamestmartin.wasteland.offlineplayer.CommandLastSeen;
import me.jamestmartin.wasteland.offlineplayer.CommandUUID; import me.jamestmartin.wasteland.offlineplayer.CommandUUID;
@ -22,15 +18,11 @@ class ModToolsState {
private final ManualState manualState; private final ManualState manualState;
private final CommandBan commandBan; private final CommandBan commandBan;
private final CommandBans commandBans;
private final CommandInfractions commandInfractions; private final CommandInfractions commandInfractions;
private final CommandKick commandKick; private final CommandKick commandKick;
private final CommandLastSeen commandLastSeen; private final CommandLastSeen commandLastSeen;
private final CommandModLog commandModLog; private final CommandAuditLog commandAuditLog;
private final CommandMute commandMute; private final CommandMute commandMute;
private final CommandMutes commandMutes;
private final CommandUnban commandUnban;
private final CommandUnmute commandUnmute;
private final CommandUUID commandUUID; private final CommandUUID commandUUID;
private final CommandWarn commandWarn; private final CommandWarn commandWarn;
@ -41,15 +33,11 @@ class ModToolsState {
InfractionStore store = null; InfractionStore store = null;
this.commandBan = new CommandBan(store, config.getDurations().getBansConfig()); this.commandBan = new CommandBan(store, config.getDurations().getBansConfig());
this.commandBans = new CommandBans();
this.commandInfractions = new CommandInfractions(); this.commandInfractions = new CommandInfractions();
this.commandKick = new CommandKick(store); this.commandKick = new CommandKick(store);
this.commandLastSeen = new CommandLastSeen(); this.commandLastSeen = new CommandLastSeen();
this.commandModLog = new CommandModLog(); this.commandAuditLog = new CommandAuditLog();
this.commandMute = new CommandMute(store, config.getDurations().getMutesConfig()); this.commandMute = new CommandMute(store, config.getDurations().getMutesConfig());
this.commandMutes = new CommandMutes();
this.commandUnban = new CommandUnban();
this.commandUnmute = new CommandUnmute();
this.commandUUID = new CommandUUID(); this.commandUUID = new CommandUUID();
this.commandWarn = new CommandWarn(store); this.commandWarn = new CommandWarn(store);
} }
@ -58,15 +46,11 @@ class ModToolsState {
manualState.register(plugin); manualState.register(plugin);
plugin.getCommand("ban").setExecutor(commandBan); plugin.getCommand("ban").setExecutor(commandBan);
plugin.getCommand("bans").setExecutor(commandBans);
plugin.getCommand("infractions").setExecutor(commandInfractions); plugin.getCommand("infractions").setExecutor(commandInfractions);
plugin.getCommand("kick").setExecutor(commandKick); plugin.getCommand("kick").setExecutor(commandKick);
plugin.getCommand("lastseen").setExecutor(commandLastSeen); plugin.getCommand("lastseen").setExecutor(commandLastSeen);
plugin.getCommand("modlog").setExecutor(commandModLog); plugin.getCommand("auditlog").setExecutor(commandAuditLog);
plugin.getCommand("mute").setExecutor(commandMute); plugin.getCommand("mute").setExecutor(commandMute);
plugin.getCommand("mutes").setExecutor(commandMutes);
plugin.getCommand("unban").setExecutor(commandUnban);
plugin.getCommand("unmute").setExecutor(commandUnmute);
plugin.getCommand("uuid").setExecutor(commandUUID); plugin.getCommand("uuid").setExecutor(commandUUID);
plugin.getCommand("warn").setExecutor(commandWarn); plugin.getCommand("warn").setExecutor(commandWarn);
} }
@ -79,7 +63,7 @@ class ModToolsState {
plugin.getCommand("infractions").setExecutor(null); plugin.getCommand("infractions").setExecutor(null);
plugin.getCommand("kick").setExecutor(null); plugin.getCommand("kick").setExecutor(null);
plugin.getCommand("lastseen").setExecutor(null); plugin.getCommand("lastseen").setExecutor(null);
plugin.getCommand("modlog").setExecutor(null); plugin.getCommand("auditlog").setExecutor(null);
plugin.getCommand("mute").setExecutor(null); plugin.getCommand("mute").setExecutor(null);
plugin.getCommand("mutes").setExecutor(null); plugin.getCommand("mutes").setExecutor(null);
plugin.getCommand("unban").setExecutor(null); plugin.getCommand("unban").setExecutor(null);

View File

@ -4,9 +4,9 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.config.DurationsConfig; import me.jamestmartin.wasteland.modtools.config.DurationsConfig;
import me.jamestmartin.wasteland.modtools.infraction.Infraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.NewInfraction;
import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
public class CommandBan extends CommandIssueInfraction { public class CommandBan extends CommandIssueInfraction {
public CommandBan(InfractionStore store, DurationsConfig durations) { public CommandBan(InfractionStore store, DurationsConfig durations) {
@ -14,12 +14,12 @@ public class CommandBan extends CommandIssueInfraction {
} }
@Override @Override
protected InfractionType getType() { protected SentenceType getType() {
return InfractionType.BAN; return SentenceType.BAN;
} }
@Override @Override
protected void applyInfraction(CommandSender sender, Infraction infraction) { protected void applyInfraction(CommandSender sender, NewInfraction infraction) {
Player recipient = infraction.getRecipient().getPlayer(); Player recipient = infraction.getRecipient().getPlayer();
if (recipient != null) { if (recipient != null) {
recipient.kickPlayer(infraction.getMessage()); recipient.kickPlayer(infraction.getMessage());

View File

@ -1,15 +0,0 @@
package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandBans implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -10,9 +10,9 @@ import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.config.DurationsConfig; import me.jamestmartin.wasteland.modtools.config.DurationsConfig;
import me.jamestmartin.wasteland.modtools.infraction.Duration; import me.jamestmartin.wasteland.modtools.infraction.Duration;
import me.jamestmartin.wasteland.modtools.infraction.Infraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.NewInfraction;
import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
abstract class CommandIssueInfraction implements CommandExecutor { abstract class CommandIssueInfraction implements CommandExecutor {
protected final InfractionStore store; protected final InfractionStore store;
@ -41,13 +41,13 @@ abstract class CommandIssueInfraction implements CommandExecutor {
} }
Player issuer = sender instanceof Player ? (Player) sender : null; Player issuer = sender instanceof Player ? (Player) sender : null;
Optional<Infraction> maybe = parseArgs(getType(), issuer, args); Optional<NewInfraction> maybe = parseArgs(getType(), issuer, args);
if (maybe.isEmpty()) { if (maybe.isEmpty()) {
// TOOD: better error messages // TOOD: better error messages
sender.sendMessage("Invalid syntax."); sender.sendMessage("Invalid syntax.");
return false; return false;
} }
Infraction infraction = maybe.get(); NewInfraction infraction = maybe.get();
if (infraction.getDuration().compareTo(maxDuration) == 1) { if (infraction.getDuration().compareTo(maxDuration) == 1) {
sender.sendMessage("You are not allowed you issue an infraction of duration " + infraction.getDuration() + "."); sender.sendMessage("You are not allowed you issue an infraction of duration " + infraction.getDuration() + ".");
@ -57,15 +57,15 @@ abstract class CommandIssueInfraction implements CommandExecutor {
} }
applyInfraction(sender, infraction); applyInfraction(sender, infraction);
store.addInfraction(infraction); store.issueInfraction(infraction);
return false; return false;
} }
protected abstract InfractionType getType(); protected abstract SentenceType getType();
protected abstract void applyInfraction(CommandSender sender, Infraction infraction); protected abstract void applyInfraction(CommandSender sender, NewInfraction infraction);
private static Optional<Infraction> parseArgs(InfractionType type, OfflinePlayer issuer, String[] args) { private static Optional<NewInfraction> parseArgs(SentenceType type, OfflinePlayer issuer, String[] args) {
// TODO // TODO
return null; return null;
} }

View File

@ -3,9 +3,9 @@ package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.infraction.Infraction; import me.jamestmartin.wasteland.modtools.infraction.NewInfraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
public class CommandKick extends CommandIssueInfraction { public class CommandKick extends CommandIssueInfraction {
public CommandKick(InfractionStore store) { public CommandKick(InfractionStore store) {
@ -13,12 +13,12 @@ public class CommandKick extends CommandIssueInfraction {
} }
@Override @Override
protected InfractionType getType() { protected SentenceType getType() {
return InfractionType.KICK; return SentenceType.KICK;
} }
@Override @Override
protected void applyInfraction(CommandSender sender, Infraction infraction) { protected void applyInfraction(CommandSender sender, NewInfraction infraction) {
Player player = infraction.getRecipient().getPlayer(); Player player = infraction.getRecipient().getPlayer();
if (player == null) { if (player == null) {
sender.sendMessage("That player is not online!"); sender.sendMessage("That player is not online!");

View File

@ -4,9 +4,9 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.config.DurationsConfig; import me.jamestmartin.wasteland.modtools.config.DurationsConfig;
import me.jamestmartin.wasteland.modtools.infraction.Infraction; import me.jamestmartin.wasteland.modtools.infraction.NewInfraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
public class CommandMute extends CommandIssueInfraction { public class CommandMute extends CommandIssueInfraction {
public CommandMute(InfractionStore store, DurationsConfig durations) { public CommandMute(InfractionStore store, DurationsConfig durations) {
@ -14,12 +14,12 @@ public class CommandMute extends CommandIssueInfraction {
} }
@Override @Override
protected InfractionType getType() { protected SentenceType getType() {
return InfractionType.MUTE; return SentenceType.MUTE;
} }
@Override @Override
protected void applyInfraction(CommandSender sender, Infraction infraction) { protected void applyInfraction(CommandSender sender, NewInfraction infraction) {
Player player = infraction.getRecipient().getPlayer(); Player player = infraction.getRecipient().getPlayer();
if (player != null) { if (player != null) {
player.sendMessage(infraction.getMessage()); player.sendMessage(infraction.getMessage());

View File

@ -1,15 +0,0 @@
package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandMutes implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -1,46 +0,0 @@
package me.jamestmartin.wasteland.modtools.commands;
import java.util.Optional;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.infraction.Infraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType;
public abstract class CommandRemoveInfraction implements CommandExecutor {
protected final InfractionStore store;
public CommandRemoveInfraction(InfractionStore store) {
this.store = store;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Player issuer = sender instanceof Player ? (Player) sender : null;
Optional<Infraction> maybe = parseArgs(getType(), issuer, args);
if (maybe.isEmpty()) {
// TOOD: better error messages
sender.sendMessage("Invalid syntax.");
return false;
}
Infraction infraction = maybe.get();
applyInfraction(sender, infraction);
store.addInfraction(infraction);
return false;
}
protected abstract InfractionType getType();
protected abstract void applyInfraction(CommandSender sender, Infraction infraction);
private static Optional<Infraction> parseArgs(InfractionType type, OfflinePlayer issuer, String[] args) {
// TODO
return null;
}
}

View File

@ -1,15 +0,0 @@
package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandUnban implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -1,15 +0,0 @@
package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandUnmute implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -3,9 +3,9 @@ package me.jamestmartin.wasteland.modtools.commands;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import me.jamestmartin.wasteland.modtools.infraction.Infraction; import me.jamestmartin.wasteland.modtools.infraction.NewInfraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore; import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
public class CommandWarn extends CommandIssueInfraction { public class CommandWarn extends CommandIssueInfraction {
public CommandWarn(InfractionStore store) { public CommandWarn(InfractionStore store) {
@ -13,12 +13,12 @@ public class CommandWarn extends CommandIssueInfraction {
} }
@Override @Override
protected InfractionType getType() { protected SentenceType getType() {
return InfractionType.WARN; return SentenceType.WARN;
} }
@Override @Override
protected void applyInfraction(CommandSender sender, Infraction infraction) { protected void applyInfraction(CommandSender sender, NewInfraction infraction) {
Player player = infraction.getRecipient().getPlayer(); Player player = infraction.getRecipient().getPlayer();
if (player != null) { if (player != null) {
player.sendMessage(infraction.getMessage()); player.sendMessage(infraction.getMessage());

View File

@ -1,6 +1,7 @@
package me.jamestmartin.wasteland.modtools.infraction; package me.jamestmartin.wasteland.modtools.infraction;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -170,4 +171,15 @@ public class Duration implements Comparable<Duration> {
return Long.compare(getSeconds().get(), other.getSeconds().get()); return Long.compare(getSeconds().get(), other.getSeconds().get());
} }
public static Duration fromDates(Date begin, Date end) {
return new Duration(end.getTime() - begin.getTime());
}
public static Duration fromDates(Date begin, Optional<Date> end) {
if (end.isEmpty()) {
return INFINITY;
}
return fromDates(begin, end.get());
}
} }

View File

@ -1,56 +1,139 @@
package me.jamestmartin.wasteland.modtools.infraction; package me.jamestmartin.wasteland.modtools.infraction;
import java.util.Date; import java.util.Date;
import java.util.Optional;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
public class Infraction { public class Infraction {
private final InfractionType type; /** The unique identifier of this infraction. */
private final OfflinePlayer issuer; private final int id;
/** The type of sentence the player received: ban, kick, mute, or warning. */
private final SentenceType type;
/** The moderator who issued the sentence. (Or empty, if it was issued by the console.) */
private final Optional<OfflinePlayer> issuer;
/** The player who committed the infraction. */
private final OfflinePlayer recipient; private final OfflinePlayer recipient;
/** The time that the sentence was issued. */
private final Date issued; private final Date issued;
private final Duration duration; /** The time that the sentence takes effect. */
private final Date start;
/** The time that the sentence naturally expires, if ever. */
private final Optional<Date> expiry;
/** The information about this infraction's repeal, if it was repealed. */
private final Optional<Repeal> repeal;
/** If this sentence was commuted, the identifier for the original infraction. */
private final Optional<Integer> original;
/** The section identifier for the rule that was violated. */
private final String rule; private final String rule;
/** The reason that an infraction was issued. */
private final String reason; private final String reason;
public Infraction(InfractionType type, OfflinePlayer issuer, OfflinePlayer recipient, Date issued, Duration duration, String rule, String reason) { public Infraction(
int id,
SentenceType type,
Optional<OfflinePlayer> issuer,
OfflinePlayer recipient,
Date issued,
Date start,
Optional<Date> expiry,
Optional<Repeal> repeal,
Optional<Integer> original,
String rule,
String reason
) {
this.id = id;
this.type = type; this.type = type;
this.issuer = issuer; this.issuer = issuer;
this.recipient = recipient; this.recipient = recipient;
this.issued = issued; this.issued = issued;
this.duration = duration; this.start = start;
this.expiry = expiry;
this.repeal = repeal;
this.original = original;
this.rule = rule; this.rule = rule;
this.reason = reason; this.reason = reason;
} }
public InfractionType getType() { /** @return The unique identifier for this infraction. */
public int getId() {
return id;
}
/** @return The {@link SentenceType type of sentence} the player received: ban, kick, mute, or warning. */
public SentenceType getType() {
return type; return type;
} }
public OfflinePlayer getIssuer() { /** @return The moderator who issued the sentence. (Or empty, if it was issued by the console.) */
public Optional<OfflinePlayer> getIssuer() {
return issuer; return issuer;
} }
/** @return The player who committed the infraction. */
public OfflinePlayer getRecipient() { public OfflinePlayer getRecipient() {
return recipient; return recipient;
} }
/** @return The time the sentence was issued. */
public Date getIssued() { public Date getIssued() {
return issued; return issued;
} }
public Duration getDuration() { /**
return duration; * @return
* <p>The time that the sentence first takes effect.
* <p>
* Generally, this will be the same time that it was issued;
* however, if a sentence is commuted, the new infraction starts at the same time,
* but will actually have a time issued *after* the sentence started!
*/
public Date getStart() {
return start;
} }
/** @return The time the infraction naturally expires if not repealed, if ever. */
public Optional<Date> getExpiry() {
return expiry;
}
/**
* @return The duration that the sentence would last if not repealed.
* @see #getActualDuration()
*/
public Duration getOriginalDuration() {
return Duration.fromDates(getStart(), getExpiry());
}
/** @return The information about this infraction's repeal, if it was repealed. */
public Optional<Repeal> getRepeal() {
return repeal;
}
/** @return If this sentence was commuted, the identifier for the original infraction. */
public Optional<Integer> getOriginal() {
return original;
}
/**
* @return The duration the sentence would last if it has been repealed, or otherwise the original duration.
* @see #getOriginalDuration()
*/
public Duration getActualDuration() {
return Duration.fromDates(getStart(), getRepeal().map(Repeal::getIssued).or(() -> getExpiry()));
}
/** @return The rule that was violated. */
public String getRule() { public String getRule() {
return rule; return rule;
} }
/** @return The reason that the infraction was issued. */
public String getReason() { public String getReason() {
return reason; return reason;
} }
/** @return The message that the player will get sent with information about their sentence. */
public String getMessage() { public String getMessage() {
// TODO // TODO
return null; return null;

View File

@ -5,21 +5,42 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
public interface InfractionProvider { public interface InfractionProvider {
Set<Infraction> getActiveInfractions(Optional<UUID> player, Optional<InfractionType> type); /**
* @param player The player who committed the infractions, or any player.
* @param type The type of sentence given, or any sentence type.
* @return The un-expired, un-repealed sentences matching the provide criteria.
*/
Set<Infraction> getActiveInfractions(Optional<UUID> player, Optional<SentenceType> type);
/** @return All un-expired, un-repealed sentences. */
default Set<Infraction> getActiveInfractions() { default Set<Infraction> getActiveInfractions() {
return getActiveInfractions(Optional.empty(), Optional.empty()); return getActiveInfractions(Optional.empty(), Optional.empty());
} }
/**
* @param player The player who committed the infractions.
* @return The player's un-expired, un-repealed sentences.
*/
default Set<Infraction> getActiveInfractions(UUID player) { default Set<Infraction> getActiveInfractions(UUID player) {
return getActiveInfractions(Optional.of(player), Optional.empty()); return getActiveInfractions(Optional.of(player), Optional.empty());
} }
default Set<Infraction> getActiveInfractions(UUID player, InfractionType type) { /**
* @param player The player who committed the infractions.
* @param type The type of sentence given.
* @return The player's un-expired, un-repealed sentences of the given type.
*/
default Set<Infraction> getActiveInfractions(UUID player, SentenceType type) {
return getActiveInfractions(Optional.of(player), Optional.of(type)); return getActiveInfractions(Optional.of(player), Optional.of(type));
} }
default Set<Infraction> getActiveInfractions(InfractionType type) { /**
* @param type The type of sentence given.
* @return All un-expired, un-repealed sentences of the given type.
*/
default Set<Infraction> getActiveInfractions(SentenceType type) {
return getActiveInfractions(Optional.empty(), Optional.of(type)); return getActiveInfractions(Optional.empty(), Optional.of(type));
} }
// TODO: Provide *in*active infractions which were never cleared.
} }

View File

@ -1,8 +1,36 @@
package me.jamestmartin.wasteland.modtools.infraction; package me.jamestmartin.wasteland.modtools.infraction;
import java.util.Optional;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public interface InfractionStore extends InfractionProvider { public interface InfractionStore extends InfractionProvider {
void addInfraction(Infraction infraction); /**
void removeInfractions(OfflinePlayer player, InfractionType type); * @param infraction The new infraction to add to the database.
* @see NewInfraction#NewInfraction(SentenceType, java.util.Optional, OfflinePlayer, Duration, String, String) Create a new infraction.
* @see NewInfraction#NewInfraction(java.util.Optional, Duration, Infraction) Commute an old infraction.
* @see NewInfraction#NewInfraction(SentenceType, java.util.Optional, OfflinePlayer, java.util.Optional, Duration, java.util.Optional, String, String) Update an old infraction.
*/
void issueInfraction(NewInfraction infraction);
/**
* <p>Clear a previously-issued infraction.
* <p>This will clear the infraction from the player's record, but not from the audit log.
* @param id The identifier of the infraction to clear.
* @param issuer The moderator who cleared the infraction.
* @see RepealType#CLEARED
* @see #appealInfraction(OfflinePlayer, SentenceType)
*/
void clearInfraction(int id, Optional<Player> issuer);
/**
* <p>Accept an appeal for a previously-issued infraction.
* <p>This will remove the sentence from the player, but not remove it from their record.
* @param id The identifier of the infraction which was appealed.
* @param issuer The moderator who accepted the appeal.
* @see RepealType#APPEALED
* @see #clearInfraction(int, Optional)
*/
void appealInfraction(int id, Optional<Player> issuer);
} }

View File

@ -0,0 +1,145 @@
package me.jamestmartin.wasteland.modtools.infraction;
import java.util.Date;
import java.util.Optional;
import org.bukkit.OfflinePlayer;
public class NewInfraction {
/** The type of sentence the player received: ban, kick, mute, or warning. */
private final SentenceType type;
/** The moderator who issued the sentence. (Or empty, if it was issued by the console.) */
private final Optional<OfflinePlayer> issuer;
/** The player who committed the infraction. */
private final OfflinePlayer recipient;
/** The time that the sentence takes effect, or empty if it starts immediately. */
private final Optional<Date> start;
/** The duration of the sentence. */
private final Duration duration;
/** If this sentence was commuted, the identifier for the original infraction. */
private final Optional<Integer> original;
/** The section identifier for the rule that was violated. */
private final String rule;
/** The reason that an infraction was issued. */
private final String reason;
public NewInfraction(
SentenceType type,
Optional<OfflinePlayer> issuer,
OfflinePlayer recipient,
Optional<Date> start,
Duration duration,
Optional<Integer> original,
String rule,
String reason
) {
this.type = type;
this.issuer = issuer;
this.recipient = recipient;
this.start = start;
this.duration = duration;
this.original = original;
this.rule = rule;
this.reason = reason;
}
public NewInfraction(
SentenceType type,
Optional<OfflinePlayer> issuer,
OfflinePlayer recipient,
Duration duration,
String rule,
String reason
) {
this.type = type;
this.issuer = issuer;
this.recipient = recipient;
this.start = Optional.empty();
this.duration = duration;
this.original = Optional.empty();
this.rule = rule;
this.reason = reason;
}
/** Update an old infraction with a new expiry. */
public NewInfraction(
Optional<OfflinePlayer> issuer,
Duration duration,
Infraction original
) {
this.type = original.getType();
this.issuer = issuer;
this.recipient = original.getRecipient();
this.start = Optional.of(original.getStart());
this.duration = duration;
this.original = Optional.of(original.getId());
this.rule = original.getRule();
this.reason = original.getReason();
}
/** Update an old infraction with a new expiry and reason. */
public NewInfraction(
Optional<OfflinePlayer> issuer,
Duration duration,
Infraction original,
String rule,
String reason
) {
this.type = original.getType();
this.issuer = issuer;
this.recipient = original.getRecipient();
this.start = Optional.of(original.getStart());
this.duration = duration;
this.original = Optional.of(original.getId());
this.rule = rule;
this.reason = reason;
}
/** @return The {@link SentenceType type of sentence} the player received: ban, kick, mute, or warning. */
public SentenceType getType() {
return type;
}
/** @return The moderator who issued the sentence. (Or empty, if it was issued by the console.) */
public Optional<OfflinePlayer> getIssuer() {
return issuer;
}
/** @return The player who committed the infraction. */
public OfflinePlayer getRecipient() {
return recipient;
}
/**
* @return The time that the sentence first takes effect, or empty if it starts immediately.
*/
public Optional<Date> getStart() {
return start;
}
/** @return The time the infraction naturally expires if not repealed, if ever. */
public Duration getDuration() {
return duration;
}
/** @return If this sentence was commuted, the identifier for the original infraction. */
public Optional<Integer> getOriginal() {
return original;
}
/** @return The rule that was violated. */
public String getRule() {
return rule;
}
/** @return The reason that the infraction was issued. */
public String getReason() {
return reason;
}
/** @return The message that the player will get sent with information about their sentence. */
public String getMessage() {
// TODO
return null;
}
}

View File

@ -0,0 +1,33 @@
package me.jamestmartin.wasteland.modtools.infraction;
import java.util.Date;
import java.util.Optional;
import org.bukkit.OfflinePlayer;
public class Repeal {
private final RepealType type;
private final Optional<OfflinePlayer> issuer;
private final Date issued;
public Repeal(RepealType type, Optional<OfflinePlayer> issuer, Date issued) {
this.type = type;
this.issuer = issuer;
this.issued = issued;
}
/** The way in which the infraction was repealed. */
public RepealType getType() {
return type;
}
/** The player who issued the repeal. */
public Optional<OfflinePlayer> getIssuer() {
return issuer;
}
/** The time the repeal was issued. */
public Date getIssued() {
return issued;
}
}

View File

@ -0,0 +1,11 @@
package me.jamestmartin.wasteland.modtools.infraction;
/** The way in which which an infraction was removed. */
public enum RepealType {
/** The player was cleared of wrongdoing, and this infraction will be removed from their record. */
CLEARED,
/** The player's sentence was reduced, and replaced with another infraction. */
COMMUTED,
/** The player's sentence was removed based on appeal, but the infraction will remain on their record. */
APPEALED;
}

View File

@ -1,6 +1,6 @@
package me.jamestmartin.wasteland.modtools.infraction; package me.jamestmartin.wasteland.modtools.infraction;
public enum InfractionType { public enum SentenceType {
BAN(true), BAN(true),
KICK(false), KICK(false),
MUTE(true), MUTE(true),
@ -8,7 +8,7 @@ public enum InfractionType {
private final boolean hasduration; private final boolean hasduration;
private InfractionType(boolean hasDuration) { private SentenceType(boolean hasDuration) {
this.hasduration = hasDuration; this.hasduration = hasDuration;
} }

View File

@ -6,37 +6,60 @@ import java.util.UUID;
import me.jamestmartin.wasteland.modtools.infraction.Duration; import me.jamestmartin.wasteland.modtools.infraction.Duration;
import me.jamestmartin.wasteland.modtools.infraction.Infraction; import me.jamestmartin.wasteland.modtools.infraction.Infraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionType; import me.jamestmartin.wasteland.modtools.infraction.SentenceType;
public interface ModLogProvider { public interface AuditLogProvider {
/**
* @param duration The maximum time ago that an infraction was issued or repealed.
* @param issuer The moderator who issued an infraction.
* @param recipient The player who committed the infraction.
* @param type The type of sentence issued.
* @return All infractions that were issued or repealed fitting the provided criteria.
*/
Set<Infraction> getInfractions( Set<Infraction> getInfractions(
Duration duration, Duration duration,
Optional<UUID> issuer, Optional<UUID> issuer,
Optional<UUID> recipient, Optional<UUID> recipient,
Optional<InfractionType> type Optional<SentenceType> type
); );
/** All infractions that have ever been issued. */
default Set<Infraction> getInfractions() { default Set<Infraction> getInfractions() {
return getInfractions(Duration.INFINITY); return getInfractions(Duration.INFINITY);
} }
default Set<Infraction> getInfractions(InfractionType type) { /**
* @param type The type of sentence issued.
* @return All sentences of the given type that have ever been issued.
*/
default Set<Infraction> getInfractions(SentenceType type) {
return getInfractions(Duration.INFINITY, type); return getInfractions(Duration.INFINITY, type);
} }
/**
* @param duration The maximum time ago that an infraction was issued or repealed.
* @return All sentences that have ever been issued or repealed within the duration provided.
*/
default Set<Infraction> getInfractions(Duration duration) { default Set<Infraction> getInfractions(Duration duration) {
return getInfractions(duration, Optional.empty(), Optional.empty(), Optional.empty()); return getInfractions(duration, Optional.empty(), Optional.empty(), Optional.empty());
} }
default Set<Infraction> getInfractions(Duration duration, InfractionType type) { /**
* @param duration The maximum time ago that an infraction was issued or repealed.
* @param type The type of sentence issued.
* @return All sentences of the given type that have ever been issued or repealed within the duration provided.
*/
default Set<Infraction> getInfractions(Duration duration, SentenceType type) {
return getInfractions(duration, Optional.empty(), Optional.empty(), Optional.of(type)); return getInfractions(duration, Optional.empty(), Optional.empty(), Optional.of(type));
} }
// TODO: Javadoc for all these variants.
default Set<Infraction> getInfractionsIssuedTo(UUID player) { default Set<Infraction> getInfractionsIssuedTo(UUID player) {
return getInfractionsIssuedTo(Duration.INFINITY, player); return getInfractionsIssuedTo(Duration.INFINITY, player);
} }
default Set<Infraction> getInfractionsIssuedTo(UUID player, InfractionType type) { default Set<Infraction> getInfractionsIssuedTo(UUID player, SentenceType type) {
return getInfractionsIssuedTo(Duration.INFINITY, player, type); return getInfractionsIssuedTo(Duration.INFINITY, player, type);
} }
@ -44,7 +67,7 @@ public interface ModLogProvider {
return getInfractions(duration, Optional.empty(), Optional.of(player), Optional.empty()); return getInfractions(duration, Optional.empty(), Optional.of(player), Optional.empty());
} }
default Set<Infraction> getInfractionsIssuedTo(Duration duration, UUID player, InfractionType type) { default Set<Infraction> getInfractionsIssuedTo(Duration duration, UUID player, SentenceType type) {
return getInfractions(duration, Optional.empty(), Optional.of(player), Optional.of(type)); return getInfractions(duration, Optional.empty(), Optional.of(player), Optional.of(type));
} }
@ -52,7 +75,7 @@ public interface ModLogProvider {
return getInfractionsIssuedBy(Duration.INFINITY, player); return getInfractionsIssuedBy(Duration.INFINITY, player);
} }
default Set<Infraction> getInfractionsIssuedBy(UUID player, InfractionType type) { default Set<Infraction> getInfractionsIssuedBy(UUID player, SentenceType type) {
return getInfractionsIssuedBy(Duration.INFINITY, player, type); return getInfractionsIssuedBy(Duration.INFINITY, player, type);
} }
@ -60,8 +83,7 @@ public interface ModLogProvider {
return getInfractions(duration, Optional.of(player), Optional.empty(), Optional.empty()); return getInfractions(duration, Optional.of(player), Optional.empty(), Optional.empty());
} }
default Set<Infraction> getInfractionsIssuedBy(Duration duration, UUID player, InfractionType type) { default Set<Infraction> getInfractionsIssuedBy(Duration duration, UUID player, SentenceType type) {
return getInfractions(duration, Optional.of(player), Optional.empty(), Optional.of(type)); return getInfractions(duration, Optional.of(player), Optional.empty(), Optional.of(type));
} }
} }

View File

@ -1,7 +1,8 @@
package me.jamestmartin.wasteland.modtools.log; package me.jamestmartin.wasteland.modtools.log;
import me.jamestmartin.wasteland.modtools.infraction.Infraction; import me.jamestmartin.wasteland.modtools.infraction.Infraction;
import me.jamestmartin.wasteland.modtools.infraction.InfractionStore;
public interface ModLogStore extends ModLogProvider { public interface AuditLogStore extends AuditLogProvider, InfractionStore {
void addInfraction(Infraction infraction); void addInfraction(Infraction infraction);
} }

View File

@ -4,7 +4,7 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
public class CommandModLog implements CommandExecutor { public class CommandAuditLog implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {

View File

@ -5,14 +5,19 @@ version: 0.1.0
api-version: 1.16 api-version: 1.16
commands: commands:
modlog: auditlog:
description: See the list of recent actions taken by moderators. description: See the list of recent actions taken by moderators.
usage: "Usage: /<command> [<duration>]" usage: "Usage: /<command> [<duration>]"
permission: wasteland.modtools.log permission: wasteland.modtools.log
permission-message: You do not have permission to view the moderator log. permission-message: You do not have permission to view the aduit log.
infractions: infractions:
description: See the list of infractions against the rules a player has committed. description: See the list of infractions against the rules a player has committed.
usage: "Usage: /<infractions> <player> [<duration>]" usage: |
Usage:
/<command> query <player> [<duration>]
/<command> commute <id> <duration> [<rule>] [<reason> ...]
/<command> clear <id>
/<command> appeal <id>
permission: wasteland.modtools.infractions permission: wasteland.modtools.infractions
permission-message: You do not have permission to view players' infractions against the rules. permission-message: You do not have permission to view players' infractions against the rules.
@ -43,16 +48,6 @@ commands:
usage: "Usage: /<command> <player> <duration> [<rule>] [<reason> ...]" usage: "Usage: /<command> <player> <duration> [<rule>] [<reason> ...]"
permission: wasteland.modtools.ban.issue permission: wasteland.modtools.ban.issue
permission-message: You do not have permission to ban players. permission-message: You do not have permission to ban players.
unban:
description: Unban a player, allowing them to join again.
usage: "Usage: /<command> <player> [<reason> ...]"
permission: wasteland.modtools.ban.pardon
permission-message: You do not have permission to unban players.
bans:
description: See the list of all players currently banned from the server.
usage: "Usage: /<command> [<player>]"
permission: wasteland.modtools.ban.list
permission-message: You do not have permission to list banned players.
kick: kick:
description: Kick a player from the server. description: Kick a player from the server.
@ -65,16 +60,6 @@ commands:
usage: "Usage: /<command> <player> <duration> [<rule>] [<reason> ...]" usage: "Usage: /<command> <player> <duration> [<rule>] [<reason> ...]"
permission: wasteland.modtools.mute.issue permission: wasteland.modtools.mute.issue
permission-message: You do not have permission to mute players. permission-message: You do not have permission to mute players.
unmute:
description: Unmute a player, allowing them to talk again.
usage: "Usage: /<command> <player> [<reason> ...]"
permission: wasteland.modtools.mute.pardon
permission-message: You do not have permission to unmute players.
mutes:
description: See the list of all players currently muted on the server.
usage: "Usage: /<command> [<player>]"
permission: wasteland.modtools.mutes.list
permission-message: You do not have permission to list muted players.
warn: warn:
description: Give a player an official (logged) warning. description: Give a player an official (logged) warning.
@ -97,12 +82,12 @@ permissions:
default: op default: op
children: children:
wasteland.modtools.infractions: true wasteland.modtools.infractions: true
wasteland.modtools.ban.list: true
wasteland.modtools.mute.list: true
wasteland.modtools.warn.list: true
wasteland.modtools.infractions: wasteland.modtools.infractions:
description: Allows you to see the list of infractions against the rules a player has committed. description: Allows you to see the list of infractions against the rules a player has committed.
default: op default: op
wasteland.modtools.infractions.clear:
description: Allows you to clear infractions from players' permanent records.
default: op
wasteland.modtools.uuid: wasteland.modtools.uuid:
description: Allows you to see the UUIDs of players with a given name on this server. description: Allows you to see the UUIDs of players with a given name on this server.
@ -151,20 +136,16 @@ permissions:
wasteland.manual.faq: true wasteland.manual.faq: true
wasteland.modtools.ban: wasteland.modtools.ban:
description: Allows you to ban or unban a player, or list active bans. description: Allows you to ban or unban a player.
default: op default: op
children: children:
wasteland.modtools.ban.issue: true wasteland.modtools.ban.issue: true
wasteland.modtools.ban.list: true
wasteland.modtools.ban.pardon: true wasteland.modtools.ban.pardon: true
wasteland.modtools.ban.issue: wasteland.modtools.ban.issue:
description: Allows you to issue bans. description: Allows you to issue bans.
default: op default: op
wasteland.modtools.ban.list: wasteland.modtools.ban.commute:
description: Allows you to list all active bans. description: Allows you to commute bans.
default: op
wasteland.modtools.ban.pardon:
description: Allows you to remove bans.
default: op default: op
wasteland.modtools.kick: wasteland.modtools.kick:
@ -172,20 +153,15 @@ permissions:
default: op default: op
wasteland.modtools.mute: wasteland.modtools.mute:
description: Allows you to mute or unmute a player, or list active mutes. description: Allows you to mute or unmute a player.
default: op
children: children:
wasteland.modtools.mute.issue: true wasteland.modtools.mute.issue: true
wasteland.modtools.mute.list: true
wasteland.modtools.mute.pardon: true wasteland.modtools.mute.pardon: true
wasteland.modtools.mute.issue: wasteland.modtools.mute.issue:
description: Allows you to issue mutes. description: Allows you to issue mutes.
default: op default: op
wasteland.modtools.mute.list: wasteland.modtools.mute.commute:
description: Allows you to list active mutes. description: Allows you to commute mutes.
default: op
wasteland.modtools.mute.pardon:
description: Allows you to remove mutes.
default: op default: op
wasteland.modtools.warn: wasteland.modtools.warn: