# Moderation

Muting and unmuting players in voice chat — timed and permanent mutes, reasons, a server-wide mute, and what a muted player sees

Source: https://socketsvc.vercel.app/server/moderation
A **mute** stops a player talking. Their microphone, soundboard, instrument and group audio are all dropped at the relay, and nothing about what they *hear* changes.

That asymmetry is the whole design. Someone being moderated has to be able to hear what is being said to them and about them; a mute that also deafened would be a disconnection with extra steps, and it would leave them with no idea why.

<Callout title="This replaces revoking socketsvc.speak">
  Before these commands existed, the only way to silence someone was to take the [`socketsvc.speak`](/server/permissions#permission-nodes) node away in a permissions plugin. That is permanent, carries no reason, tells the player nothing at all, and needs an operator who can edit permissions. `socketsvc.speak` is still the right tool for a standing policy — "this rank does not get voice" — and a mute is the right tool for a person who is doing something now.
</Callout>

## Commands

| Command | Purpose |
| --- | --- |
| `/ssvc-server mute <player> [duration] [reason...]` | Mutes them. With no duration, until somebody lifts it |
| `/ssvc-server unmute <player>` | Lifts an individual mute |
| `/ssvc-server mutes` | Lists everybody currently muted, with reasons, who issued each and how long is left |
| `/ssvc-server mute-all <on\|off>` | Silences everybody without `socketsvc.mute.exempt` |

All four need `socketsvc.admin.mute` on top of the `socketsvc.admin` that gates `/ssvc-server` itself. Both default to op, so an unconfigured server behaves as though the extra node were not there — it exists so moderation can be handed to people who are not being handed the rest of the console.

```text
> /ssvc-server mute Alex 2h shouting in spawn
Muted Alex in voice chat for 2h, reason: shouting in spawn

> /ssvc-server mutes
SocketSVC voice mutes: 1
  Alex - 1h 58m left, by Steve, reason: shouting in spawn
    f81d4fae-7dec-11d0-a765-00a0c91e6bf6

> /ssvc-server unmute Alex
Alex can talk in voice chat again.
```

### Durations

`30s`, `45m`, `2h`, `7d`, `1w`, or several run together as `2h30m`. `perm`, `permanent` and `forever` mean a mute that stands until somebody lifts it, which is also what you get by leaving the duration out.

A **bare number is deliberately not a duration**. Whether `mute Alex 30` means thirty seconds or thirty minutes is not something anybody should have to guess, and a reason beginning with a number is far more likely than either. A trailing run with no unit — `2h30` — is refused for the same reason: taking the `2h` and dropping the `30` would mute for a different length than was asked for.

Whether the word after the name is a duration or the first word of the reason is decided by whether it parses as one, so a reason really starting `5m` loses that word. The reply always says which duration and which reason were understood, rather than leaving you to find out from the player.

### Muting somebody who has logged off

People leave when a moderator turns up, so this is the ordinary case rather than the exception.

On **Paper**, a name that server has seen before resolves from the profile cache, so `mute Alex 1d` works whether or not Alex is online. A name it has never seen is reported as unknown.

On the **server mods** a name only resolves while that player is online. Either way both platforms accept a raw UUID, and `/ssvc-server mutes` prints the UUID under every name for exactly that — copy it out of the list to unmute somebody who is not around.

### Mute-all

`mute-all on` silences everybody who does not hold `socketsvc.mute.exempt` — for an announcement, an event, or a room that has got out of hand. It is not a config setting; it is a switch you flip and flip back, and it survives a restart like any other mute, because a restart in the middle of an event should not un-silence the room.

Individual mutes are unaffected by it in both directions: `mute-all off` does not lift them, and unmuting one person does not turn it off.

## What a muted player sees

Two things, because either on its own is not enough.

An **action-bar notice** the moment they try to talk, at most once every five seconds:

```text
You are muted in voice chat for another 1h 58m: shouting in spawn
```

And a **persistent HUD indicator** — the crossed-out microphone with `Muted by server` beside it, and the time left when the mute is timed. The words matter: without them a silenced player sees the same crossed-out microphone their own mute button produces, reaches for that button, and learns nothing.

<Callout type="warn" title="Older clients see only the notice">
  The HUD indicator needs a client new enough to understand the `SSVC_MUTE_V1` message ([Voice protocol](/server/voice-protocol#server-mute-v1)). An older one ignores the line, exactly as it ignores any text message it does not recognise, and is left with the action-bar notice — which is what it would have had anyway. Nothing about the mute itself depends on the client.
</Callout>

## Permissions

| Node | Default | Purpose |
| --- | --- | --- |
| `socketsvc.admin.mute` | op | Use the four commands above |
| `socketsvc.mute.exempt` | op | Keep talking through `mute-all`, and cannot be muted individually |

`socketsvc.mute.exempt` bypasses **only** the server-wide mute. An individual mute is an explicit decision somebody made about that player and is not undone by a permission node — `/ssvc-server mute` refuses to issue one against an exempt player in the first place, and says so. The alternative would be a mute sitting in the list doing nothing, which is the worst of both.

Like every other SocketSVC node it is read once per tick, and a player who has just joined and has not been ticked yet is treated as **not** exempt — an unticked player must not briefly talk over a room somebody has just silenced.

## Where mutes are stored

| Server side | File |
| --- | --- |
| Paper | `plugins/SocketSVC/mutes.txt` |
| Fabric/Quilt, NeoForge, Forge | `config/socketsvc-mutes.txt` |

A mute that lives only in memory is undone by logging out, so every change is written immediately. The file is written to a sibling temporary file and moved into place, so a crash mid-write leaves the previous list rather than a half-written one.

It is a plain text file and can be read, but editing it by hand is not the intended route: the server holds the same list in memory and will overwrite yours on its next change. `/ssvc-server reload` re-reads it, because a reload builds a replacement listener.

A mute whose time ran out while the server was down does not come back. One row that cannot be parsed is skipped and reported in the log rather than taking the rest of the file with it — one corrupt line must not hand everybody else their voice back.

## For addons

`SocketSvcApi` and `SocketSvcModApi` expose the same registry the relay reads from version 3 onwards, so an addon and the audio path cannot disagree about who is silenced. See [Spatial audio API](/server/spatial-audio-api).

```java
SocketSvcApi api = /* ... */;
api.muteVoice(player, 2 * 60 * 60 * 1000L, "shouting in spawn", "AntiSpam");
boolean silenced = api.isVoiceMuted(player); // individual mute OR mute-all they are not exempt from
api.unmuteVoice(player);
```