Content generated with AI — it may contain mistakes.

Features

Punishments

How punishments made by LiteBans, LibertyBans, AdvancedBan or BanManager on the proxy reach ExyliaStaff, the Redis stream they go into, and importing a ban plugin's history.

ExyliaStaff owns punishments. The proxy carries the part only the proxy can see: punishments made by a ban plugin installed on the proxy, including a plain /ban typed there and every removal. They are written to a Redis stream on the same Redis and key-prefix as the bridge, and ExyliaStaff reads them.

Supported ban plugins

PluginVelocityBungeeCordoriginId
LiteBansyesyesban:<id>, mute:<id>, warn:<id>, kick:<id>
LibertyBansyesyes<id>, one sequence for every type
AdvancedBanno (it has no Velocity build)yes<id>; a live kick kick:<uuid without dashes>:<start>; an import's history-only entry history:<id>
BanManageryesyesban:<id>, mute:<id>, warn:<id>, kick:<id>; an import's lifted bans and mutes banrecord:<id>, muterecord:<id>

None is required. Each is a soft dependency, loaded before this plugin when present, and each hook checks that its plugin is there before touching any of its classes. A hooked plugin is announced at startup:

Recording LITEBANS punishments made on this proxy.

A hook that fails is logged as Could not hook <ORIGIN>: <error> and skipped; the rest of the plugin keeps working.

What is recorded

PluginAddedRemoved
LiteBansEvery entry added.Every entry removed, with who and why.
LibertyBansEvery punishment.Every pardon, with who; LibertyBans gives no reason.
AdvancedBanEvery punishment.Every revoke; AdvancedBan names nobody who revoked.
BanManagerBans, mutes, warnings, kicks.Unbans and unmutes that were not cancelled, read last, with who and why.

Entries that name no player — address-only bans — are skipped: the stream's target is always a uuid. An IP ban that does name a player is recorded as IPBAN. Names a plugin does not keep (LibertyBans keeps none, of the target or of the staff member) are filled from whoever is online at that moment, else left empty; LibertyBans' console is written as Console.

The stream

Key: <prefix>:punish:external. Each entry is one field, json:

XADD <prefix>:punish:external MAXLEN ~ 200000 * json <json>
{"v":1,"kind":"added","origin":"LITEBANS","originId":"ban:123","type":"TEMPBAN",
 "target":"<uuid>","targetName":"Steve","staff":null,"staffName":"Console",
 "reason":"Hacking","server":"proxy","created":1730000000000,"expires":1730086400000,
 "removedAt":0,"removedBy":null,"removeReason":null,"import":false}
FieldMeaning
vFormat version, 1.
kindadded or removed.
originLITEBANS, LIBERTYBANS, ADVANCEDBAN or BANMANAGER.
originIdStable per origin; see the table above. Cut to 64 characters.
typeBAN, TEMPBAN, IPBAN, MUTE, TEMPMUTE, WARN or KICK.
target, targetNameThe punished player.
staff, staffNameWho did it; staff is null for the console.
reasonCut to 255 characters.
serverThe ban plugin's own origin server when it keeps one (LiteBans), else proxy.
created, expires, removedAtEpoch milliseconds; 0 means never.
removedBy, removeReasonFor removed entries.
importtrue for entries written by an import: ExyliaStaff sends no alerts or player notices for them.

The stream keeps about the last 200,000 entries; ExyliaStaff reads it with the consumer group exyliastaff.

Live entries go through a single writer thread, so a ban plugin never waits on Redis and a removal never overtakes its punishment. A write is tried three times, one and two seconds apart; one that still fails is logged with its JSON, never dropped silently:

Could not record a punishment in exylia:punish:external (<error>): {"v":1,...}

Importing history

A ban plugin's whole history can be copied into the stream once, so ExyliaStaff knows every punishment made before it was installed. From any backend running ExyliaStaff:

/staffadmin import <plugin>

with exyliastaff.admin. ExyliaStaff imports locally when that ban plugin is installed on the backend, and otherwise asks the proxy's punish-import module. The proxy answers at once and reads the history on a thread of its own, logging as it goes:

Importing LITEBANS history into the punishment stream...
Importing LITEBANS: 1000 entries so far.
Imported LITEBANS: 48213 entries.
PluginHow the history is read
LiteBansStraight from its four tables (bans, mutes, warnings, kicks), 500 rows at a time by id.
LibertyBansThrough its API, oldest first, 500 at a time; active punishments are read first to tell which are lifted.
AdvancedBanThe active table, then the history of everything no longer active. Each is loaded whole.
BanManagerEvery table through BanManager's own cursors, history tables included.

An active punishment is written as added. A lifted or expired one is written as added followed by removed; kicks are only added. When the plugin keeps no removal date (LibertyBans, AdvancedBan) the removal is dated at the punishment's end if it ran out, else at the moment of the import.

Only one import per plugin runs at a time; a second request answers <ORIGIN> import already running. An import that fails stops with The <ORIGIN> import stopped after <n> entries: <error>; what was written stays written.

Run an import once

Every run writes the whole history again. ExyliaStaff deduplicates on origin and originId, but there is no reason to run it twice.

Something missing on this page? Tell us on Discord