Skip to main content
Three layers run server-side on every outbound message, in this order:
  1. Rate limit (per-player rolling window).
  2. Duplicate detection (same message twice within a short window).
  3. Blocked-word filter (normalised substring match, leet-aware).
A fourth layer - the profanity escalation ladder - sits on top of the blocked-word filter and tracks repeat offenders. All four are configured in config.lua under the antispam and profanityEscalation blocks. The defaults are reasonable for a roleplay server; the rest of this page covers the tuning knobs.

Rate limit + duplicate detection

config.lua

Per-channel exemption

Some channels - typically /me - benefit from repeated text. Players legitimately type “*sighs*”, “*nods*”, “*looks around*” in quick succession, and the duplicate detection would flag them as flood. Tick Bypass antispam in the Category Manager for that channel. The flag stores as bypassAntispam = true on the category row. When set:
  • Rate limit + duplicate checks are skipped for messages in that channel.
  • Length cap still applies (structural safety, not behavioural).
  • Blocked-word filter still applies (slurs are slurs regardless of channel).
  • Profanity escalation still applies on blocked-word hits.
See Categories for the full per-channel flag reference.

Blocked-word filter

Substring-matched against a normalised version of the message, so common evasion tricks (leet, spacing, padding) collapse to the same canonical form as the bare word.
config.lua

Normalisation pipeline

Both the message body and each blocked-word entry are normalised the same way before comparison:
  1. Lowercased - "SLUR""slur"
  2. Leet substitutions - 1→i, 3→e, 4→a, 5→s, 7→t, 8→b, 9→g, @→a, $→s, !→i, |→i, +→t, 0→o, 2→z, 6→g. "61ur""slur".
  3. Strip non-letters - punctuation, spaces, digits not in the leet map. "s l u r""slur". "s.l.u.r""slur".
  4. Collapse runs - (.)\1+\1. Any run of 2+ identical letters collapses to one. "slurrrr""slu". The blocked-word list is normalised the same way, so 'slur' becomes 'slu' too and the haystack "slu..." contains the needle "slu". Match.
The collapse-to-one rule has one known false positive: the country / place name “Niger” (single g) normalises to the same canonical form as the slur. If your server can’t accept that collision, remove the slur from blockedWords and live with the evasion gap.

Customising the list

Add or remove entries freely. The shipped list is intentionally conservative - a handful of very short / ambiguous terms (e.g. 'gay', 'fag' outside 'faggot', 'fat', 'ass', 'rape') are deliberately omitted because they collide with legitimate English words at substring level. Add them yourself if you want stricter matching and accept the false positives.

Profanity escalation

Counts blocked-word hits per-license and runs an action when the count crosses a rung.
config.lua

How the ladder works

  • A player trips the blocked-word filter → counter increments → highest matching rung fires.
  • Counter is per-license, persists across reconnects within the session.
  • Counter resets to zero after resetAfterMs of clean behaviour (no blocked-word hits). Default: 1 hour.
  • Players with awoken_chat.bypass ACE never increment the counter.

Rung actions

The shipped ladder (warn → mute(60s) → kick at 6 hits) is intentionally gentle for first-offence cases. Tune the hits thresholds, durationMs, and resetAfterMs to match your server’s culture.

Customising the ladder

Rungs are evaluated bottom-to-top - the highest rung whose hits threshold is matched fires. So you can add intermediate steps:

Bypass permission

awoken_chat.bypass is the only ACE that’s NOT implied by awoken_chat.admin. Granting “admin” alone gives Category Manager access + admin-delete + clear, but does NOT skip the blocked-word filter. Bypass is intentional, opt-in only.
A player with bypass:
  • Never trips the rate limit / duplicate / blocked-word filter.
  • Doesn’t increment the profanity escalation counter.
  • Can send the exact same message back-to-back without throttling.
Reserve for trusted staff who need to discuss moderation cases (“the player said X, here’s what they typed verbatim”) without their own messages getting filtered.

Audit trail

Every blocked-word hit, rate-limit hit, and duplicate flood posts a Discord embed to webhooks.violations if configured. See Webhooks for the embed shape and what’s included.