Documentation
Mallard is a Discord moderation bot with a built-in scripting engine: everything the bot does, from slash commands to event reactions to scheduled jobs, is a script you can read, edit, and write yourself from the dashboard, either as JavaScript or as blocks that compile to it. This page walks through how it works; the sections below hold the full API reference.
Overview
Out of the box, Mallard ships with moderation commands: warn, kick, ban (including temp bans), timeout, slowmode, unban, and per-user notes. Each one posts to a mod-log channel and keeps an audit trail. Beyond that, every piece of guild behavior (slash commands, reactions to Discord events, and scheduled/timed jobs) is authored as a "GuildScript": a snippet of JavaScript stored per guild and run through an embedded JS engine, rather than hardcoded per-guild logic in the bot itself.
Scripts are written and managed entirely from the dashboard, using a full code editor with autocomplete against the same API reference documented on this site. No hosting, no deploy step: saving a script takes effect immediately.
Easy Mode and Advanced Mode
A script's editor offers two modes for its body. Advanced is the code
editor above, and is what everything else on this site documents. Easy
builds the script from a palette of blocks instead:
30 action blocks drawn from the same function catalogue this
reference is generated from, plus an If with an optional otherwise
branch and a block that stops the run early. A script may hold up to
60 blocks across every branch, nested at most
3 deep.
An If tests one of two things. Either it compares a value against typed
text (is, is not, contains, does not contain,
is greater than, is less than, or the one-sided is empty /
is not empty, which is how a script checks a setting before using it), or it
calls a check: a function that answers yes or no, such as
hasRole. Either test can be inverted, so "does not have the role" is the
same block with the check flipped.
Easy Mode is not a second runtime. The blocks compile to ordinary JavaScript, shown below the canvas as you build, and that JavaScript is the only artifact that ever runs: nothing on the execution path knows the blocks exist. So a block script has the same triggers, the same filters, the same budgets and the same logs and traces as one written by hand.
The palette is filtered by trigger, so a block calling a function that trigger cannot
reach is never offered. Values from the event, the command's own options and the
server's settings sit above the canvas as chips: drag one onto a field and it leaves a
{{path}} token that compiles to the matching ctx path. A text
field takes a token anywhere in what you typed, so
Banned <@{{args.user}}> is a field value and compiles to the
concatenation you would have written by hand. Channel and role fields are pickers
instead, listing your server's real channels and roles, and hold one whole value.
How it works
One process holds the Discord gateway connection and decides what should run; a pool of interchangeable runners does the running:
- A Discord gateway event (a message, a member joining, a reaction, …) or a slash command arrives at the bot.
- The bot looks up enabled scripts matching the guild and the trigger type, applies that script's filters, and checks the guild's hourly budgets. Anything admitted is written to a job queue. Because that decision is made once, by the single process that sees every event, the budget counting stays honest no matter which runner picks the job up.
- A runner claims the job, usually within milliseconds. Runners are anonymous and no guild belongs to any of them, but only one job per guild is ever in flight across the whole pool, and each guild's jobs are claimed oldest-first, so two scripts in one server never race on the key-value store. Different guilds run concurrently.
- The script runs end-to-end through an embedded JavaScript engine (Jint). Every Discord action it calls (sending a message, banning a user, adding a role, …) and every key-value write happens live, in call order, the moment the script calls it. There's no all-or-nothing rollback: a script that errors partway keeps whatever actions and writes it already made.
- Once the script finishes (or errors, or times out), an execution record is saved with its status, duration, how long it waited to be claimed, and any error, viewable on the guild's Logs page.
Full reference
Everything below mirrors the bot's live script API definition, so it matches what the dashboard's editor autocompletes against.