Repository navigation
Controller Panel
English | 繁體中文
This page covers the now-playing controller panel: what it is, how to edit its layout with /controller, and the exact template syntax it understands.
The controller panel is the embed the bot posts (and keeps updating) in the music text channel while a track is playing. It shows the current track and a row of buttons for the actions people actually use — pause, skip, volume, repeat, and so on — so that most playback control never needs a slash command. It is the bot's Vocard-style interface: a data-driven layout, rendered per guild, rather than a fixed embed.
Whether the panel shows buttons at all, and whether it uses the full or minimal layout, are set with /npbuttons and /nplayout (see Commands). This page is about the layout itself — what goes inside it.
/controller opens the editor for the current server's panel layout. It is an Admin-only command (bot owner, or a member with MANAGE_SERVER — see Permissions-and-Server-Settings). The layout you set here applies to this guild only; there is no global layout override from inside Discord.
A layout is a small template: mostly literal text and embed structure, with three kinds of placeholders that get replaced when the panel renders. Because the template syntax was built to match Vocard's, an existing Vocard layout can be pasted in and it renders the same way here.
| syntax | meaning |
|---|---|
{{ cond ?? yes // no }} |
conditional section |
@@t_some.key@@ |
translated text, resolved from the language files |
@@variable@@ |
substituted value |
Evaluation order is fixed: conditionals are resolved first, then translations, then variables. A conditional can therefore choose between two blocks that each contain their own translation keys or variables, and those are filled in afterwards.
This is the part that catches people out. The renderer recognises exactly four @@variable@@ placeholders:
| variable | value |
|---|---|
@@volume@@ |
current volume |
@@queue_length@@ |
number of tracks queued |
@@loop_mode@@ |
repeat mode's friendly name, or Off
|
@@track_name@@ |
current track title, or None
|
There is no fifth. Names like artist, duration, requester, and thumbnail do not exist as variables here, however plausible any of them look next to the four above — wrapping one in @@...@@ markers does not make it work. A reference to anything not in the table is not an error and produces no warning: it simply renders as an empty string, silently, in place. If a layout looks wrong — a blank line where a value should be, a sentence with a gap in it — the first thing to check is whether it references a variable outside this list.
Ten button actions exist:
previous, pause, skip, stop, shuffle, repeat, favorite, volup, voldown, back
Four button styles exist (colours, in Discord's button-style sense):
red, green, blue, grey (gray is also accepted as a spelling of the last one)
Four of the ten actions are not static — they change appearance depending on the current player state:
-
pause— reflects whether playback is currently playing or paused -
repeat— reflects the current loop mode -
favorite— reflects whether the current track is already favourited -
stop— has a distinct pressed/confirm state
The remaining six actions (previous, skip, shuffle, volup, voldown, back) always render the same way regardless of state. For a layout, this matters mainly for pause and repeat: whatever label or emoji is configured for the button is what shows for every state, but the button's rendered style/state indicator still reflects the underlying model, so testing a layout means checking it in more than one player state — for example while paused as well as while playing — not just at the moment it was saved.
A layout that shows only the track name and a pause/skip/stop row, with no conditional logic at all:
Now playing: @@track_name@@
Queue: @@queue_length@@ track(s)
Buttons: pause (grey), skip (grey), stop (red)
This renders as a plain two-line embed body — the literal text Now playing: followed by whatever @@track_name@@ resolves to (or None if nothing is playing), then the queue count on the next line — with three buttons underneath. There is no branching, so it looks the same in every state except for the substituted values themselves.
{{ queue_length ?? @@t_player.queued@@ // @@t_player.empty@@ }}
This picks between two translated strings depending on whether queue_length is non-zero (truthy): if there are queued tracks, it renders the translation at player.queued; if the queue is empty, it renders the translation at player.empty instead. Because translations are resolved after the conditional, each branch can carry its own language-file key, and the branch that is not taken is never looked up. A branch is not limited to a single translation — @@queue_length@@ or other variables can sit alongside @@t_...@@ text within either side of the //, since conditionals, translations, and variables are resolved as separate passes over whatever text ends up selected.
Commands for /controller, /npbuttons, and /nplayout syntax. Permissions-and-Server-Settings for who can run /controller. Getting-Started for getting the bot running in the first place. Troubleshooting if a layout renders blank or a button does not appear to do anything.