5
Custom Commands
Logan Fick edited this page 2026-02-18 11:25:20 -05:00

Custom Commands

The custom_commands module lets moderators create, edit, and delete chat commands at runtime without code changes or restarts. Commands support a placeholder system for dynamic responses.

This is a built-in module that ships with Owlbot and is enabled by default.

Quick Start

Create a command:

!addcommand !hello Hello, $(user)! Welcome to the stream.

Now anyone typing !hello in chat sees "Hello, Alice! Welcome to the stream." (with their name substituted). Edit it, add an alias, set a cooldown:

!editcommand !hello Hey $(user), glad you're here!
!addalias !hello !hi
!commandcooldown !hello 30

Delete it when it's no longer needed:

!deletecommand !hello

Management Commands

Command Aliases Usage Permission Description
!addcommand !addcmd !addcommand !name response text Moderator Create a new custom command.
!editcommand !editcmd !editcommand !name new response Moderator Edit an existing command's response.
!deletecommand !delcmd !deletecommand !name Moderator Delete a custom command.
!commandmodonly !cmdmodonly !commandmodonly !name <on|off> Moderator Toggle moderator-only access.
!resetcommand !resetcmd !resetcommand !name Moderator Reset a command's use counter to 0.
!editcounter !editcount !editcounter name <value> Moderator Set, increment, or decrement a named counter.
!commandcooldown !cmdcooldown !commandcooldown !name <seconds> Moderator Set a cooldown in seconds (0 to disable).
!addalias !addalias !command !alias Moderator Add an alias to an existing command.
!removealias !removealias !alias Moderator Remove an alias from a command.
!listcommands !listcmds !listcommands Everyone Send a link to the command list web page.

Command Names

Command names can only contain lowercase letters, numbers, and underscores. The leading ! in the name argument is optional (both !addcommand !hello ... and !addcommand hello ... work). Names are case-insensitive.

If a custom command name conflicts with a command from another module, creation is rejected.

Placeholders

Custom command responses support placeholders that are replaced at runtime.

Placeholder Description Example
$(1) through $(9) Command arguments by position. Empty string if not provided. !greet $(1) with !greet Alice produces Alice
$(count) Number of times the command has been used (incremented before display). This command has been used $(count) times.
$(count name) Increment a named counter by 1 and display its value. Supports modifiers: +n (increment), -n (decrement), or n (set). $(count deaths) increments and shows the deaths counter. $(count deaths +5) increments by 5. $(count deaths 0) resets to 0.
$(getcount name) Display a named counter's value without changing it. Returns 0 if the counter doesn't exist. $(getcount deaths) shows the current value of the deaths counter.
$(user) Display name of the user who ran the command. Hello, $(user)!
$(rand start stop) Random integer between start and stop (inclusive). $(rand 1 100) produces 42
$(countdown date) Human-readable time remaining until a date. $(countdown Dec 25 2025 12:00:00 AM EST) produces 3 days 4 hours
$(countup date) Human-readable time elapsed since a date. $(countup Jan 1 2024 12:00:00 AM UTC) produces 365 days 3 hours
\$(...) Escaped. Outputs the literal placeholder text without expanding it. \$(count) produces $(count)

If a placeholder has invalid arguments at runtime (e.g., $(rand abc) or $(getcount) with no name), the entire response is replaced with an error message describing the problem.

Unknown placeholder names are left as-is in the output.

Nesting

Placeholders can be nested inside the arguments of other placeholders. Inner placeholders are resolved first, and their results are passed as arguments to the outer placeholder.

!addcommand !customrand $(rand $(1) $(2))

Now !customrand 1 10 produces a random number between 1 and 10. The inner $(1) and $(2) resolve to the user's arguments, then $(rand 1 10) produces the final result.

Multi-level nesting also works:

!addcommand !deeprand $(rand 1 $(rand 5 10))

This picks a random number between 5 and 10, then picks a random number between 1 and that result.

Nesting is limited to a configurable maximum depth (default 4). Beyond the limit, $( is treated as literal text. The placeholder name itself must always be literal text. Constructions like $($(1)) are not valid.

Escaping applies to the entire placeholder group including any nested content. \$(rand $(1) $(2)) outputs the literal text $(rand $(1) $(2)) without resolving anything inside.

Date Format

$(countdown) and $(countup) use the following date format:

Mon DD YYYY HH:MM:SS AM/PM TZ

For example: Dec 25 2025 12:00:00 AM EST

If the target date is in the past for $(countdown), it returns "0 seconds". The same applies to $(countup) when the date is in the future.

Supported timezones (full list): ACDT, ACST, ACT, ACWST, ADT, AEDT, AEST, AFT, AKDT, AKST, ALMT, AMST, AMT, ANAT, AQTT, ART, AST, AWST, AZOST, AZOT, AZT, BIT, BIOT, BNT, BOT, BRST, BRT, BST, BTT, CAT, CCT, CDT, CEST, CET, CHADT, CHAST, CHOT, CHOST, CHST, CHUT, CIST, CKT, CLST, CLT, COST, COT, CST, CVT, CWST, CXT, DAVT, DDUT, DFT, EASST, EAST, EAT, ECT, EDT, EEST, EET, EGST, EGT, EST, FET, FJT, FKST, FKT, FNT, GALT, GAMT, GET, GFT, GILT, GIT, GMT, GST, GYT, HAEC, HDT, HKT, HMT, HOVST, HOVT, HST, ICT, IDLW, IDT, IOT, IRDT, IRKT, IRST, IST, JST, KALT, KGT, KOST, KRAT, KST, LHST, LINT, MAGT, MART, MAWT, MDT, MEST, MET, MHT, MIST, MIT, MMT, MSK, MST, MUT, MVT, MYT, NCT, NDT, NFT, NOVT, NPT, NST, NT, NUT, NZDT, NZDST, NZST, OMST, ORAT, PDT, PET, PETT, PGT, PHOT, PHST, PHT, PKT, PMDT, PMST, PONT, PST, PWT, PYST, PYT, RET, ROTT, SAKT, SAMT, SAST, SBT, SCT, SDT, SGT, SLST, SRET, SRT, SST, SYOT, TAHT, TFT, THA, TJT, TKT, TLT, TMT, TOT, TRT, TST, TVT, ULAST, ULAT, UTC, UYST, UYT, UZT, VET, VLAT, VOLT, VOST, VUT, WAKT, WAST, WAT, WEST, WET, WGST, WGT, WIB, WIT, WITA, WST, YAKT, YEKT.

Cooldowns

Cooldowns are global per-command, not per-user. Setting !commandcooldown !hello 30 means !hello can only fire once every 30 seconds regardless of who invokes it. A cooldown of 0 disables it.

New commands default to a 5-second cooldown. This default can be changed via the default_cooldown module configuration option.

When a command is on cooldown, the user receives a private system message with the remaining wait time.

Moderator-Only Commands

By default, custom commands are available to everyone. Use !commandmodonly !name on to restrict a command to moderators. When a non-moderator tries to use a restricted command, they receive a private system message explaining why.

Aliases

A command can have multiple aliases. All aliases share the same response, cooldown, use counter, and permissions as the primary command. Users can invoke the command with any of its aliases.

!addcommand !hello Hello, $(user)!
!addalias !hello !hi
!addalias !hello !hey

Now !hello, !hi, and !hey all produce the same response. Removing an alias does not affect the command or its other aliases.

Deleting a command deletes all of its aliases. If you try to !deletecommand an alias, the bot will warn you and suggest using !removealias to remove just the alias or !deletecommand with the actual command name.

Alias names follow the same rules as command names (lowercase alphanumeric and underscores). An alias cannot share a name with any existing command or alias from any module.

Web UI

The module serves a command list page at /owlbot/custom_commands/list showing all custom commands in a table with their aliases, responses, cooldowns, and permissions. The !listcommands command sends a link to this page in chat.

Examples

# Basic greeting
!addcommand !hello Hello, $(user)! Welcome to the stream.

# Dice roll
!addcommand !roll $(user) rolled a $(rand 1 6)!

# Stream uptime tracker
!addcommand !uptime Stream has been live for $(countup Jan 15 2025 07:00:00 PM EST).

# Event countdown
!addcommand !nextstream Next stream: $(countdown Jan 20 2025 07:00:00 PM EST)

# Use counter
!addcommand !hype The hype train has been called $(count) times! HYPE!

# Command with arguments
!addcommand !shoutout Go check out $(1)! They're awesome.

# Restrict to moderators
!commandmodonly !shoutout on

# Add aliases and set cooldown
!addalias !shoutout !so
!commandcooldown !shoutout 10

# Named death counter (increments each use)
!addcommand !deaths We have died $(count deaths) times.

# Display death count without incrementing
!addcommand !deathcheck Total deaths so far: $(getcount deaths)

# Reset a named counter
!editcommand !deaths $(count deaths 0) deaths. Counter reset!

# Multiple named counters in one command
!addcommand !score Wins: $(getcount wins) | Losses: $(getcount losses)
!addcommand !win We won! Total wins: $(count wins)
!addcommand !loss We lost. Total losses: $(count losses)

# Set a counter to an absolute value
!editcounter deaths 0

# Increment a counter
!editcounter deaths +1

# Decrement a counter
!editcounter deaths -3

# Nested placeholders: random range from arguments
!addcommand !customrand $(rand $(1) $(2))

# Multi-level nesting
!addcommand !deeprand $(rand 1 $(rand 5 10))

# Show literal placeholder text (escaped)
!addcommand !placeholderhelp Use \$(user) to insert your name.