
StaffChat
A private chat channel for your server's staff team.
Список изменений
[1.0.0] — 2026-04-16
Initial release. Server-side Fabric 1.21.1 private chat channel for staff members, with zero hard mod dependencies beyond Fabric API.
Added
Core chat mechanic
/staffchat(or/sc) — toggles staff-chat mode for the caller. While toggled, every regular chat line the player sends is routed to staff only instead of global chat. Title pulse on toggle on/off + persistent actionbar● Staff Chat Modeso nobody forgets they're in stealth mode./staffchat <message>— one-off send without changing toggle state. Good for quick replies during normal play.@<message>quick prefix — typing@at the start of a regular chat line routes the rest to staff only. Works even with toggle mode off. The prefix character is configurable (chat.quick-prefix) — set to""to disable entirely, or change to#/~/ etc. to avoid clashes with mention syntax.- Permission node:
staffchat.true(default OP 4 fallback) gates sending AND receiving. Without a permissions plugin, OPs see the channel automatically; with LuckPerms you can grant it per-group.
Mention pings
@<playername>inside a staff message pings the mentioned player (must also holdstaffchat.true) with:- An actionbar flash (
★ Mentioned by <sender>) - A chime (default
entity.experience_orb.pickup, all configurable — sound id, volume, pitch, actionbar template)
- An actionbar flash (
- Self-mentions are skipped. Offline / non-staff names are skipped.
Discord webhook mirroring (opt-in)
- Built-in async webhook POST to a configured Discord webhook URL.
Every staff message is forwarded as the sender's name + avatar (via
https://mc-heads.net/avatar/<uuid>). Async executor so no tick lag; configurable retry budget on transient failures. - Off by default. Admins opt in by setting
discord.enabled = trueand fillingdiscord.webhook-url. - Privacy guarantee: staff messages never leak through other
chat-bridge mods. The mod suppresses the outgoing message at
ALLOW_CHAT_MESSAGE(so nothing in the normal chat pipeline sees it) and re-delivers it as a system message (not a chat message) to each staff recipient — chat-bridge mods that hook the chat broadcast pipeline therefore never observe it. The built-in webhook is the only intended Discord mirror for staff chat. allowed_mentions.parseis empty so a staff member typing@everyonedoes NOT actually ping the Discord server.- Misconfigured webhooks (4xx response) trigger a 5-minute log-spam suppression window so a stale token doesn't drown the console.
LuckPerms metadata prefix
- If LuckPerms is loaded, each sender's
[Prefix]metadata is resolved reflectively and prepended to their display name (soft dep — no LuckPerms = no crash). - Falls back to a config-based
prefixes { id = "..." }map keyed by the higheststaffchat.rank.<id>permission the player holds, in declaration order. Final fallback: blank.
Security
Lifted the defensive patterns from the PokeBuilder project:
- Permission re-checked on every message send — a staff member demoted between messages N and N+1 silently drops off the recipient list on N+1 (no error leaks the demotion).
- Input sanitizer strips C0 control characters (
\u0000–\u001F), strips the raw section-sign (\u00A7) to prevent bypassing the&code path, caps messages atchat.max-length(default 256), and rejects messages that are entirely formatting codes. - Rate limit: sliding 30-second window, configurable
security.rate-limit-per-30s(default 10). Exceeded → sender gets a "slow down" reply, message dropped server-side, audit log records the rate-limit event. staffchat.bypass.rate-limitbypass node useshasExplicit— OPs don't auto-bypass unless LuckPerms explicitly grants it.- Audit log (
logs/staffchat-audit.log): every staff send gets an 8-character base36 tx id + timestamp + sender + content + recipient count + outcome (DELIVERED / RATE_LIMITED / SANITIZED / DROPPED). Atomic append viaStandardOpenOption.APPENDso concurrent writes don't clobber. - Quick-prefix gated by the same
staffchat.truecheck — non-staff players typing@hellosee their message in global chat as normal, with no permission-leak signal.
Commands
/staffchat Toggle staff chat mode (staffchat.true)
/staffchat <message> One-off send (no toggle change) (staffchat.true)
/staffchat reload Reload config from disk (staffchat.admin.reload, OP 3)
/staffchat list List currently-online staff (staffchat.admin.list, OP 3)
/sc Alias for /staffchat toggle
/sc <message> Alias for /staffchat <message>
Configuration
config/staffchat/config.conf is a HOCON file. Notable knobs:
chat.format— placeholder-based message template ({prefix} {sender} {message} {time})chat.quick-prefix— the@character that triggers quick-send; set to""to disablechat.toggle-indicator+chat.toggle-on-title/chat.toggle-off-title— visual polish on mode flipmentions.*— ping behavior on@usernameinside staff messagesdiscord.*— the opt-in webhook mirrorprefixes { id = "..." }— fallback prefix map when LuckPerms is absentsecurity.rate-limit-per-30s/security.log-*— anti-spam + auditpersist-toggled-state— remember who had /staffchat on across restarts (stored indata/staffchat/toggled.json)message-locale—"auto" | "en_us" | "fr_fr" | ...
Message overrides go in a separate messages { ... } block. The mod
only persists your actual deltas there (not the entire locale base),
so the config file stays small.
i18n
English ships bundled. Drop any config/staffchat/lang/<locale>.json
to add a new language without rebuilding the jar. Resolution order:
config-dir → bundled resource → bundled en_us fallback.
Permissions (summary)
| Node | Default | Purpose |
|---|---|---|
staffchat.true | OP 4 fallback | Send + receive in the staff channel |
staffchat.bypass.rate-limit | false (explicit only) | Ignore the rate limit |
staffchat.admin.reload | OP 3 | /staffchat reload |
staffchat.admin.list | OP 3 | /staffchat list |
staffchat.rank.<id> | — | Fallback prefix tier when LuckPerms absent |
Requirements
- Minecraft 1.21.1
- Fabric Loader ≥ 0.16.0
- Fabric API
- Java 21
Everything else is bundled in the jar:
- Fabric Permissions API (v0.3.1)
- SpongePowered Configurate HOCON (v4.1.2)
- Typesafe Config (v1.4.1)
LuckPerms is optional — prefix metadata is honored if present, but the mod works without it.
No Cobblemon / Impactor / Mega Showdown dependency — StaffChat runs on any vanilla Fabric 1.21.1 server. Happy to coexist with PokeBuilder if both are installed.
Notes for server operators
- If you already run a Discord chat-bridge mod (Styled Chat,
ServerSideUtils, simple-discord-bridge, etc.), staff messages
will NOT be mirrored by it because we suppress at
ALLOW_CHATphase. Use the built-indiscord.*webhook config to opt into exactly the Discord channel you want staff mirrored to. /op <name>alone gives that player the staff channel (OP 4 fallback onstaffchat.true). To restrict to specific players or groups, install LuckPerms and managestaffchat.trueexplicitly.data/staffchat/toggled.jsonis intentional — toggled state survives routine server restarts. Setpersist-toggled-state = falsein config to opt out.
