Skip to content
Docs
Run rooms

Roles & moderation

Give room roles, decide game roles, fill the Room management modal, and kick or ban players.

Most community rooms have some kind of staff: an owner, admins, maybe moderators. disko doesn't decide what those are. Instead, it gives your room a few building blocks:

  • Room roles: your room's own staff, such as Admin, with a badge.

  • Game roles: decide who holds the roles the loaded game declares.

  • The Room management modal: give players and staff buttons, settings and role switches.

  • Moderation: kick and ban.

Every call on this page returns a Promise that settles once the change has taken effect.

Room roles

A room role is a power over the room: kick, ban, options, the game it runs. Games never see room roles.

room.js
  • room.role(name, { label, color?, badge? }) declares a role. Declare roles from highest to lowest rank: a player's name shows the color and badge of the highest-ranking role they hold, unless the room set the player's presentation.

  • role.give(player) and role.take(player) change who holds it; role.has(player) and role.holders() read it right away.

  • room.roles.editable(rule) decides who may give and take room roles by hand in the modal: a function of { viewer, target, role }, or true/false. Without one, the room's owner may.

  • Every change, in code or by hand, arrives as role-change ({ player, role, held }).

Game roles

Games declare roles with a default, such as "host, the owner by default". In a room that decides nothing, the defaults apply: "everyone", "nobody", or "owner" (the player signed in with the room owner's account). room.game.roles.declared lists them, each with its name, label and default.

For one player, who holds a game role is decided in this order:

  1. Your room's resolver, if it answers true or false.

  2. A person in the Room management modal, where no resolver decides.

  3. The game's own give and take.

  4. The default.

Answers are kept

A resolver might be slow (it could look something up in a database), so the game never waits for it. disko asks ahead of time, at moments when the answer can change, and keeps the answers. The game reads the kept answer instantly.

When a player joins, a game loads, or the room calls refresh, disko asks the room's resolver and keeps the answer. When the game reads role.has, it reads the kept answer immediately, without waiting for the room.
room.js
  • room.game.roles.resolve(name, resolver) decides one role, and room.game.roles.resolve(resolver) every role the loaded game declares. A named resolver takes precedence; registering again replaces it. room.game.roles.remove(name?) removes one, or the catch-all without a name.

  • A resolver receives { player, role, game } and returns true, false, or undefined to leave it to a person and then the game. Errors are not caught: they reach Node's uncaughtException, and the game decides.

  • disko asks again when a player joins, a game loads, resolvers change, and on room.game.roles.refresh({ player?, role? }). Until the first answer arrives, the game reads the role as not held.

  • room.game.roles.editable(rule) decides who may give and take game roles by hand, as room.roles.editable does for room roles.

  • To change anything else about a game, such as its match length, use the events it documents with room.emit.

Room management modal

Every player has a Room button in the play client. It opens a modal with three sections: the player list, the room's options, and actions. A player's detail view also lists their room roles and game roles, with switches for viewers who may change them.

Your room doesn't draw this modal. It describes what's in it (which actions exist, which options, who may see or use them) and the client draws it the same way in every room. So players always find moderation and settings in a familiar place.

  • Player actions appear when someone clicks a player, such as Kick.

  • Room actions appear in the Actions section, such as Clear bans.

  • Options are settings, such as which game to play or whether guests may join.

room.js
  • Player actions (room.actions.player(id, definition)) appear in a player's detail view, room actions (room.actions.room(id, definition)) in the Actions section. A definition has a label (1–48 bytes), an optional confirm (true, or { reason: true } for an optional reason field), an optional visible predicate (omitted: everyone), and run.

  • Options come in four formats: room.options.toggle, choice (1–32 choices), number (min, max, optional step) and text (maxLength up to 128, optional secret, whose value is never sent to viewers). Each has a label, the current value, and optional visible, editable (omitted: nobody, so the row is read-only) and change. room.options.set(id, value) changes a value from the room; every viewer sees values live.

  • visible and editable are checked again before run or change, so a stale modal cannot act. A predicate that throws denies. When run or change throws, the action or change is rejected and the viewer sees the error message.

  • Ids are 1–64 bytes, and defining an action with the same id replaces it. room.actions.remove("player" | "room", id) and room.options.remove(id) take entries away. disko asks visible and editable again whenever a room role changes hands; call room.actions.refresh({ viewer? }) when other facts behind them change.

Presentation and moderation

Presentation is how a player's name looks everywhere it appears: a color and a short badge. Room roles set it for you; setPresentation overrides it. Moderation is removing players: a kick lets them come back, a ban doesn't.

room.js
  • player.setPresentation({ color, badge }) shows a palette role ("accent", "success", "warning", "danger" or "muted") and a short badge (at most 12 characters) wherever the player's name appears; null clears a field.

  • player.kick(reason?) disconnects a player and shows them the reason. They may rejoin.

  • player.ban({ reason? }) bans and disconnects a player: signed-in players by account, guests by an engine-held network identity the room never sees. The reason is at most 256 bytes.

  • room.bans.list() returns the bans, each with id, kind ("account" or "guest"), the player's name, the reason, a sealed export and lift(). A ban contains no address and no account.

  • Bans last for the room's lifetime. To keep them across a restart, store the sealed exports and pass each to room.bans.import(sealed) when the same room starts again. Exports are sealed with a key derived from the room's hosting token, so they import only with that token.

  • player.isOwner tells whether the player is signed in with the account that owns the room. Guests never are.

See the @disko-game/room reference for every signature.