HistoryService interface
Asynchronous session and violation history, returned as plain data records you can render anywhere.
Reach it through api.history(). Every query hits the storage backend, so everything returns a CompletableFuture — never block a Netty or main thread waiting on it. With the datastore disabled the futures complete immediately with empty data; they only fail when the backend itself fails.
| Signature | Description |
|---|---|
CompletableFuture<List<Session>> sessions(User, int limit) | Most recent sessions, newest first. |
CompletableFuture<Optional<SessionDetail>> session(User, UUID sessionId) | One session with its violations; empty when it does not exist or belongs to someone else. |
CompletableFuture<Long> totalSessions(User) | Total sessions ever recorded — used for page counts. |
Session
record Session(
UUID sessionId,
UUID playerUuid,
int ordinal, // 1 = the player's very first session ever
long startedEpochMs,
long lastActivityEpochMs,
long closedAtEpochMs, // 0 while still open
long violationCount,
int uniqueCheckCount) {
boolean closed();
long durationMs(); // max(0, lastActivity - started)
}SessionDetail and Violation
record SessionDetail(Session session, List<Violation> violations) {}
record Violation(
String check, // display name at the time it fired
String stableKey, // canonical cross-version identity
String description,
long offsetMs, // offset from session start
double vl, // violation level when it fired
String verbose) {} // verbose capture, when one was keptBuilding a session list
api.history().sessions(user, 10).thenAccept(sessions -> {
api.history().totalSessions(user).thenAccept(total -> {
long maxPages = Math.max(1, (total + 9) / 10);
for (HistoryService.Session s : sessions) {
String state = s.closed() ? "closed" : "open";
getLogger().info(String.format("#%d %s %dms %d flags (%s)",
s.ordinal(), state, s.durationMs(),
s.violationCount(), s.uniqueCheckCount()));
}
});
});Inspecting one session
api.history().session(user, sessionId).thenAccept(detail -> {
if (detail.isEmpty()) {
getLogger().info("No such session");
return;
}
detail.get().violations().forEach(v ->
getLogger().info(v.offsetMs() + "ms +"
+ String.format("%.1f", v.vl()) + " " + v.check()));
});Storage setup is covered in configuration; the commands that use this service are on commands and permissions.