Other Services
Other Services
This page covers four smaller services hanging off VoxyAPI: chat filters, analytics, anti-VPN, and cooldowns.
VoxyAPI api = VoxyAPI.getAPI();
Chat filters
api.getFilterManager() returns a FilterManager. The main entry point is processText(String), which runs the text through every loaded filter and returns a FilterResult. It is synchronous.
FilterResult result = api.getFilterManager().processText(message);
if (result.isTriggered()) {
FilterAction action = result.action(); // KEEP, GHOST, SENSOR, BLOCK
String cleaned = result.processedText(); // may be censored
String matched = result.matchedPart(); // the offending substring
// result.originalText(), result.triggeredFilter(), result.ladder()
}
FilterResult is a record exposing action(), originalText(), processedText(), matchedPart(), triggeredFilter(), and ladder(), plus the helpers isTriggered() and hasLadder(). FilterAction has four values: KEEP (leave unchanged), GHOST (show only to the sender), SENSOR (censor/modify), and BLOCK (reject).
You can also enumerate or look up individual filters:
Collection<Filter> all = api.getFilterManager().getAll();
Optional<Filter> byName = api.getFilterManager().getByName("profanity");
A Filter exposes getId(), getName(), getAction(), getLadderId() (nullable), and its compiled regex via pattern(). triggers(String) tests text against that pattern, and replace(String) masks matches with *. FilterManager also offers getById(UUID), addOrUpdate(Filter), remove(UUID) (returns boolean), and reloadFilters().
Individual words are modeled by the FilterWord record, which pairs a word() with a MatchType: NONE (fuzzy matching with character substitutions), CONTAINS_EXACTLY (the exact word, possibly surrounded by other characters), or EXACT (the standalone word only). Build one with FilterWord.of(word) or FilterWord.of(word, matchType).
Analytics
api.getAnalyticsProvider() returns an AnalyticsProvider. It builds an immutable AnalyticsSnapshot for a single server, the whole network, or a group. Both methods are synchronous.
// Pass a server name, or null for global (network-wide) statistics.
AnalyticsSnapshot global = api.getAnalyticsProvider().getSnapshot(null);
AnalyticsSnapshot lobby = api.getAnalyticsProvider().getSnapshot("lobby");
AnalyticsSnapshot survival = api.getAnalyticsProvider().getGroupSnapshot("survival");
int peak = global.getPeakToday();
int newPlayers = global.getNewPlayersToday();
long avgPlaytime = global.getAvgPlaytimeToday(); // milliseconds
double d1 = global.getRetentionD1(); // percentage
AnalyticsSnapshot is read-only and exposes a wide set of getters, including:
- Playtime averages:
getAvgPlaytimeToday(),getAvgPlaytimeYesterday(),getAvgPlaytimeLastWeek(),getAvgPlaytimeOverall()(alllongmilliseconds). - Peaks:
getPeakToday(),getPeakYesterday(),getPeakLastWeek(),getPeakOverall(), plus timestampsgetPeakTodayAt()andgetPeakYesterdayAt()(epoch millis,0when unknown). - New players:
getNewPlayersToday(),getNewPlayersYesterday(),getNewPlayersLastWeek(). - Sessions and quits:
getSessionsToday(),getSessionsYesterday(),getSessionsLastWeek(),getQuitsToday(). - Retention percentages:
getRetentionD1(),getRetentionD7(),getRetentionD30(). - Distribution:
getTopServer(),getTopServerPercent(),getVersionPercentages()(Map<Integer, Double>), andgetHourlyOnlineToday()(Map<Integer, Integer>, global snapshots only).
Anti-VPN
api.getVPNManager() returns a VPNManager. check(String ip) runs the IP through every registered check service and returns a single boolean - true means the IP is blocked (VPN, proxy, Tor, and so on), false means it is clean or every service failed. The check is IP-based and synchronous.
Check services may call external APIs, so check(String ip) can block. Never call it on the server or proxy main thread. A false result also covers the fail-open case where every service errored, so a returned false is never a guarantee the IP is clean.
boolean blocked = api.getVPNManager().check(ip);
if (blocked) {
// deny the connection, flag the player, etc.
}
To plug in your own provider, implement VPNCheckService and register it. checkIP(String) returns a VPNCheckResult:
VPNCheckService service = new VPNCheckService() {
@Override
public String name() {
return "my-service";
}
@Override
public VPNCheckResult checkIP(String ip) {
// ... perform your lookup ...
return VPNCheckResult.CLEAN;
}
};
api.getVPNManager().registerService(service);
VPNCheckResult is an enum of CLEAN, VPN, PROXY, TOR, FLAGGED, HOSTING, and API_ERROR; each carries a human-readable getReason(). VPNManager also offers unregisterService(VPNCheckService), getServices(), and reload().
Cooldowns
api.getCooldownManager() returns a CooldownManager - a network-wide, per-player cooldown engine keyed by a caller-supplied string (for example "kit") and a player UUID. A cooldown started on one server blocks the same key on every server until it expires.
State lives in Redis, so all methods are synchronous and may block; call them off the main thread where appropriate. On Redis failure the manager fails open (a cooldown is treated as inactive).
UUID player = data.getPlayerID();
// Atomically start a cooldown only if one is not already active.
boolean acquired = api.getCooldownManager().tryAcquire(player, "kit", Duration.of("1h"));
if (!acquired) {
long remaining = api.getCooldownManager().getRemaining(player, "kit"); // millis left
// deny the action and tell the player how long is left
}
tryAcquire also has an overload taking a raw long millisecond duration. Query state with isActive(UUID, String) and getRemaining(UUID, String), or fetch a richer Cooldown object:
Cooldown cooldown = api.getCooldownManager().getCooldown(player, "kit");
if (cooldown != null && cooldown.isActive()) {
String left = cooldown.toDuration().timeLeft(); // formatted string
}
Cooldown is an immutable record of key() and expiresAt() (epoch millis), with remainingMillis(), isActive(), and toDuration() for messaging. To manage cooldowns directly, use set(UUID, String, Duration) (force-set, overwriting any existing one with no atomic check) and clear(UUID, String) (remove a cooldown, e.g. an admin reset).
Key methods
| Method | Returns | Notes |
|---|---|---|
FilterManager.processText(String) |
FilterResult |
Runs text through all filters |
FilterManager.getByName(String) |
Optional<Filter> |
Case-insensitive lookup |
FilterManager.remove(UUID) |
boolean |
True if a filter was removed |
Filter.triggers(String) |
boolean |
Tests text against the pattern |
Filter.replace(String) |
String |
Masks matches with * |
AnalyticsProvider.getSnapshot(String) |
AnalyticsSnapshot |
Null server = global |
AnalyticsProvider.getGroupSnapshot(String) |
AnalyticsSnapshot |
Per-group stats |
VPNManager.check(String) |
boolean |
True if the IP is blocked |
VPNManager.registerService(VPNCheckService) |
void |
Add a custom provider |
VPNCheckService.checkIP(String) |
VPNCheckResult |
Per-service result |
CooldownManager.tryAcquire(UUID, String, Duration) |
boolean |
True if acquired |
CooldownManager.isActive(UUID, String) |
boolean |
Active cooldown check |
CooldownManager.getRemaining(UUID, String) |
long |
Millis remaining |
CooldownManager.getCooldown(UUID, String) |
Cooldown |
Null if none/expired |
CooldownManager.set(UUID, String, Duration) |
void |
Force-set, overwrites |
CooldownManager.clear(UUID, String) |
void |
Reset a cooldown |