BlazeBans API/Working with data
Players and alts
`api.players()` covers identity: who a player is, what they have been called, and which accounts appear to belong to the same person.
The five methods
api.players().recordPlayer(name, uuid, ipAddress); // CompletableFuture<Void>
api.players().profile(target); // Optional<PlayerProfile>
api.players().knownPlayerNames(); // List<String>
api.players().alts(target); // List<PlayerProfile>
api.players().nameHistory(target); // List<PlayerNameEntry>All return futures. target accepts a player name or a UUID string throughout.
Profiles
api.players().profile("Steve")
.thenAccept(found -> found.ifPresent(profile ->
getLogger().info(profile.name() + " last seen " + profile.lastSeen())
));PlayerProfile is a record with three fields:
Both nullable fields come up with imported history, where the source plugin stored less than BlazeBans does. Guard before dereferencing.
An empty Optional means BlazeBans has never seen this player, which is different from a player with no punishments.
Name history
api.players().nameHistory("Steve")
.thenAccept(entries -> entries.forEach(entry ->
getLogger().info(entry.name() + " seen " + entry.seenAt() + " via " + entry.source())
));PlayerNameEntry adds a source() to the same shape:
Useful when your plugin needs to be sure it is looking at the right person. An account that changed name last month has its history under the account, not the old name.
Known players
api.players().knownPlayerNames()
.thenAccept(names -> getLogger().info(names.size() + " known players"));Every name BlazeBans has on file. This is what powers tab completion on punishment commands.
It is unbounded and can be large on an established server. Fetch it once and cache it if you need it for completion; do not call it per keystroke.
Alts
api.players().alts("Steve")
.thenAccept(linked -> linked.forEach(profile ->
getLogger().info("Linked account: " + profile.name())
));Accounts sharing a stored IP with the target.
Three things to know before you build on this:
It needs IP storage. With privacy.store-ip-addresses: false, which is the default, this returns nothing. Your plugin cannot turn that on and should not assume it is on.
It respects the exemption list. Players on the alt blacklist, and anyone with blazebans.alts.exempt, are excluded in both directions. Do not work around that.
A shared address is not a shared person. Households, schools, and shared mobile connections all produce links. Treat the result as a signal worth a human looking at, not a conclusion.
Recording a player
api.players().recordPlayer(name, uuid, ipAddress);Records or updates a known player. Pass the raw address, or "" when you do not have one.
BlazeBans already records every joining player. Call this only when your plugin has its own legitimate source of identity data that BlazeBans would not otherwise see, such as an account linking system or a migration from another plugin's records.
Calling it on join is redundant and just adds writes.
Getting a player's IP
The methods that take an address want the raw form:
String ip = player.getAddress() == null
? ""
: player.getAddress().getAddress().getHostAddress();getAddress() can be null for a player who is disconnecting, so the guard is not optional.
Do not hash or transform it. BlazeBans applies its own settings, and a pre-hashed value will not match.
Putting it together
Flagging a joining player whose linked accounts include a banned one:
@EventHandler
public void onJoin(PlayerJoinEvent event) {
String name = event.getPlayer().getName();
this.blazeBans.players().alts(name)
.thenCompose(linked -> {
List<CompletableFuture<Boolean>> checks = linked.stream()
.filter(profile -> !profile.name().equalsIgnoreCase(name))
.map(profile -> this.blazeBans.punishments()
.isBanned(profile.name(), profile.uuid()))
.toList();
return CompletableFuture
.allOf(checks.toArray(CompletableFuture[]::new))
.thenApply(ignored -> checks.stream().anyMatch(CompletableFuture::join));
})
.thenAccept(anyBanned -> {
if (!anyBanned) {
return;
}
Bukkit.getGlobalRegionScheduler().run(this, task ->
alertStaff(name + " has a linked account that is banned."));
})
.exceptionally(error -> {
getLogger().warning("Alt check failed: " + error.getMessage());
return null;
});
}The join() calls inside allOf are safe because those futures are already complete by the time that callback runs. Calling join() before that point would block.

