Folium server integration

Folium 1.21.11 and 26.2 clients use the same control channel and line format.

Transport

Send raw UTF-8 bytes on folium:control; Minecraft already frames the plugin message. Commands are newline-separated and fields use |. Do not use Java writeUTF or add a JSON envelope.

Register the channel when the player joins, then send the controls needed for that connection.

Payload limits are 32 KiB, 256 lines, 8 KiB per field and 512 active rules. Invalid commands are ignored. Module and option IDs are case-sensitive; use stable IDs such as minimap and autogg, rather than translated labels.

Module controls and events

CommandEffect
disable|minimapTurn off and lock a module for this connection.
enable|autoggRequire a module to stay on.
disable|minimap.entitiesLock the setting's UI without changing its value.
allow|minimap.entitiesRelease a setting lock.
allow|minimapRelease the module and its settings; restore the player's preference.
resetClear rules, event subscriptions and voice configuration; retain the team roster.
listen|autogg / ignore|autoggSubscribe or unsubscribe to an event.
trigger|autoggRequest the built-in Auto GG action, subject to its locks and cooldown.

The client replies with ack|<name>|ok or ack|<name>|refused|<reason>. Events arrive as event|<name>|<payload>. enable|voice is ignored: server permission cannot turn on the player's voice module or microphone.

Rules are client-side controls, not anti-cheat enforcement. Server-imposed values are not saved as player preferences. Disconnect clears server state, including teams.

Team data

team|<uuid>|<player-name>|<team-id>
team|<uuid>                         # remove assignment, retain position
team                                # clear roster
pos|<uuid>|<x>|<y>|<z>|<health>|<dimension>
pos|<uuid>                          # forget player

Health and dimension may be omitted. Coordinates are integer block positions; health must be finite and nonnegative. Positions can be sent before a team assignment.

Voice on dedicated servers

Send each player their own short-lived ticket and connection configuration:

voice|configure|{"endpoint":"wss://voice.example.net/voice","ticket":"<signed-ticket>","listen":true,"speak":true,"iceServers":[{"urls":["stun:relay.example.net:3478"]},{"urls":["turn:relay.example.net:3478"],"username":"<temporary-user>","credential":"<temporary-password>"}]}
voice|permissions|true|false
voice|off

Keep the JSON on one line. Encode a literal pipe in JSON as \u007c. Public services need WSS and a valid TLS certificate. Plain WS is accepted only on loopback for local testing. Configuration supports up to eight ICE servers with up to eight URLs each. Production clients require a working TURN/TURNS relay; STUN-only and direct-only configurations do not work. Use short-lived TURN credentials.

listen and speak permissions apply to the receiving player. Speaking still requires that player to enable their microphone. Neither permission enables the module or mic. Configuration lasts for the game connection: send it on join, refresh tickets before expiry, and resend it after a dimension change. voice|permissions updates client behavior; also revoke access on the signaling service so existing tickets cannot continue to grant it.

The reference signaling service uses Authorization: Bearer <ticket>. The client sends voice.join with its world, talk mode and microphone state. The service derives identity, room and permissions from the verified ticket. It replies with voice.ready (member ID, permissions and peers), then sends voice.peer_joined, voice.peer_left, voice.signal, voice.whisper and ping/pong keepalives as needed. A signal is an offer or answer with type and sdp, or a candidate with type: "candidate" and nested candidate, sdpMid and sdpMLineIndex. The lower lexical member ID creates the offer. Frames are limited to 16 KiB, 60 messages per second and 16 members per room. Audio uses relay-only WebRTC. TURN hides endpoint addresses from peers, not from the game server or relay operator; it does not provide server-enforced moderation.

Voice in hosted worlds

Hosted worlds use the folium:host_voice channel over their existing authenticated Minecraft connection. The host must enable Voice chat when hosting. Each guest must also enable the local Voice module; speaking requires a locally enabled microphone. This transport needs no separate signaling server, STUN or TURN service, but the hosted-world connection itself must be reachable.

Audio is mono Opus at 24 kHz, 24 kbps VBR, in 20 ms frames. Packets carry a session UUID, authenticated sender UUID, sequence, flags, range, gain and encoded audio. The host validates the session and sequence, then forwards audio using current positions, dimensions, permissions and block lists. It applies distance attenuation; whisper range is capped at five blocks. Audio payloads are capped at 1,275 bytes.

Packet kinds are HELLO=0, WELCOME=1, STATE=2, AUDIO=3, OFF=4, BLOCK=5 and STATUS=6. Flags are LISTEN=1, SPEAK=2 and WHISPER=4. HELLO discovers support; WELCOME returns a connection nonce and host-granted permissions; STATE updates local listen, microphone and whisper state; STATUS reports current listen state. BLOCK uses the sender UUID field for the blocked player; LISTEN set adds the block and unset removes it. Only the authenticated Minecraft sender identity is forwarded. The host rejects invalid sessions, duplicate sequences, malformed Opus and over-budget packets. Limit HELLO to one per second, other control messages to 32 per second and audio to 55 packets per second per member. The host can revoke guest listen or speak permission from the hosting screen.

This is Folium voice, not Simple Voice Chat interoperability. The world owner handles forwarded audio and is a trust boundary. Playback recording does not capture this channel. The client validates packet size, rate, session nonce and sequence before forwarding audio.

Run the reference signaling service

The downloadable reference service is a small server-owned signaling service. It has no Folium account dependency. The Java ticket helper creates tickets and raw plugin-message payloads for Java server plugins.

Requirements: Node.js 22 or newer, a TLS-terminating reverse proxy, and a working STUN/TURN service for Internet clients.

npm ci
$env:VOICE_SECRET = '<random secret of at least 32 bytes>'
$env:VOICE_ADMIN_SECRET = '<different random secret of at least 32 bytes>'
npm start

The service binds to 127.0.0.1:24455 by default. Expose /voice with WebSocket upgrade enabled; keep /permissions private. PORT and BIND configure the listener. Keep both secrets on the server.

Sign an HS256 JWT with VOICE_SECRET for each authenticated player. Claims are sub (server-authenticated player UUID), name, room, world, listen, speak and exp. Tickets can last at most five minutes. Evaluate server permissions before issuing them and refresh them before they expire. Never send signing secrets to clients.

To revoke access, call POST /permissions with the separate admin bearer secret and a body such as {"sub":"<uuid>","room":"<room>","listen":false,"speak":false}. The service disconnects matching sessions and blocks old tickets for their remaining lifetime. Overrides are in memory; rotate the signing secret after a service restart if old tickets must be invalidated.

Run npm test to check authentication, signaling, room isolation and revocation. The reference service does not configure a Minecraft server or provision TURN.