A co-developed moderation stack running across community servers,
and the desktop dashboard built to support the people running them
without ever holding a server hostage. It is still out there, still
running — I am simply no longer the one shipping to it. This page is
my half, written down while it is still fresh.
Counted from my last commit rather than from memory. It has kept
moving since, so these are my numbers, not today’s. The bot is
Node; the dashboard is Python.
40,763Lines of code
157Commands
63Event handlers
60Data models
The bot25,482 lines of Node — discord.js, MongoDB, pm2 on a VPS. Older than the repository these counts come from.
The desk15,281 lines of Python — a PySide6 desktop dashboard, twelve windows, ten custom widgets, built inside the last stretch.
Last stretch13 July to 20 August 2026 — thirty-nine days, ninety-four commits. The window this repository covers, not the life of the bot.
Built byCo-developed with Louis (lode787).
The build
Ten systems, not one bot.
Described as they stood at my last commit. Every one had per-guild
configuration, its own permission model, and a way to be turned off
by the server that hosted it.
Anti-scam and anti-raid
The part I would keep if I could only keep one. A trap channel
that catches compromised accounts before they finish spraying
links. A scam-server action that blacklists the guild and its
owner, bans that owner across the other servers the bot is in,
and then leaves quietly — deliberately not a nuke, because
in a scam server the members are the victims, and it stays
reversible. Raid spam is caught at five messages in three seconds
and answered with an hour’s timeout, with per-guild exception
lists for users, roles and channels. Alongside those: a global
blacklist, an attachment-flood guard, slur logging, and an
AI-likelihood check that scores both text and images through two
separate providers.
Moderation core
Warns, cases, timeouts, kicks, mutes and purges, all landing on
one infraction record that pages by severity. Message, mod and
member logging to separate channels. Moderators earned points on
a weighted scale — a warn is worth less than a kick — tracked
weekly and posted to a log.
Softban: quarantine, not exile
Strips a member’s roles, saves them, and isolates them in
a lobby rather than removing them from the server. An appeal
channel opens once they are eligible. Leaving and rejoining does
not escape it, and deleting the lobby channel does not break it.
Lifting it hands back exactly the roles they had.
IP verification
A verification gate that hands out a role, and optionally refuses
accounts sharing an address with an already-verified member. The
address itself is never stored — only a SHA-256 hash, unique per
guild, so one server’s records say nothing about another’s.
Alt detection
Scores every member against a target across four string-similarity
algorithms, with homoglyph and leet normalisation, account age and
join proximity, avatar and banner matches, and shared roles —
weighted so a role most of the server already holds counts for
nothing.
Join-to-create voice
Joining one channel spawned a private VC with an owner control
panel — lock, hide, rename, limit, transfer, kick, block. Mods
could always see in, and were immune to both. The channel died
the moment its owner left.
Cross-server calls
Text relayed live between two servers, or three in a “mashup”.
Server names were never shown — participants got anonymous colour
labels — and every relayed message passed the word filter whatever
the sending guild had toggled off. Abuse was traceable and
timeout-able from either end.
Levelling and economy
XP with generated rank cards and per-guild styling, global and
per-server leaderboards, a wallet, and a casino behind its own
channel gate — blackjack, roulette, slots, mines, steal, and a
chip leaderboard.
And the long tail
An AI chat channel on a local model, music with a queue,
automod, custom commands, applications and staff
evaluation, a partner system with quotas, welcome and leave
messages, colour roles, verification-free utilities — AFK, QR,
background removal, sticker and emoji lifting, timers, a cipher —
and RSS listeners posting car news into channels that asked for it.
The dashboard — remote support without a backdoor
A PySide6 desktop app, about 15,000 lines, sharing the bot’s
database: server settings, insights, a control panel, bot presence
and MOTD, per-file deploys. The problem it solves is ordinary and
the answer is the interesting part — a server owner needs help,
and helping them means reaching into their server.
So it cannot. Not until that server’s owner is asked over DM and
presses approve, on a request only its owner or an administrator
can answer — being a bot developer grants nothing, because a
developer with no standing in that server is not an approver.
Every change made under a grant is appended to that grant’s own
log, and the owner is sent the whole log when access ends. Support
access you can read afterwards is support access you can trust.
Worth keeping
Four that earned their place.
Out of everything above, these are the four I would build the same
way again tomorrow.
01 — The warn pipeline
One path, so the two commands could never drift
The prefix and slash versions of a warn looked like two features
and were one: both ran the same action module, so a fix to either
was a fix to both. The count was incremented atomically in the
database rather than read-then-written, because two moderators
warning at the same second would otherwise both read three, both
write four, and step straight over the threshold.
At the threshold the bot did not act. It asked — the reply grew
Apply and Dismiss buttons, locked to the moderator who issued the
warn, so a bystander could not escalate someone else’s call. The
trigger counter reset on apply; the lifetime record never did.
Removing a warn earned no moderation points, because undoing work
is not work, and paying for it would have made warn-then-unwarn a
points farm.
The part that stuck: the interesting half of a moderation
feature is never the action. It is who is allowed to confirm
it, and what happens when two people act at once.
02 — The shell injection
A caption command that was remote code execution as root
One image command built its ffmpeg call as a single string and
handed it to exec(), which on the server means
/bin/sh -c. The user’s caption text went into that
string, and the only escaping applied was for ffmpeg’s own text
parser — so the shell’s substitution characters passed straight
through. Substitution works inside double quotes in sh,
so a payload never even had to break out of the quoting.
Nothing gated the command. Prefix commands were dispatched with no
permission check at all, each one expected to gate itself, and this
one gated nothing. So any member of any server the bot had joined
could run code as the bot process — which ran as root, and could
read the file holding the token and the database URL.
The fix was to call ffmpeg with an argument array and no shell at
all. The old escaping stayed, but it was never what had made it
safe. And it was verified on the server rather than assumed: the
same payload through exec() executed, and through
execFile() arrived as literal text.
Found while reading the code for an unrelated reason — which is
how every interesting bug that summer was found, and never by
the thing that was supposed to find it.
03 — The trap channel
Bait for compromised accounts, and the honesty it required
A compromised Discord account sprays scam links into every channel
it can reach, in order, fast. So one channel was turned into bait:
open to everyone, carrying a pinned disclaimer saying exactly what
it was, and anyone who posted in it was removed on the spot. No
real member posts in a channel like that by accident — but an
account walking the list from the top hits it almost first.
On trigger it deleted the message, sent the user a DM saying their
account was likely compromised — sent before the removal, because
Discord needs a shared server for a DM to go through — removed
them, recorded it against their infractions as a bot action rather
than a moderator’s, and purged everything they had posted
server-wide that day. Setup stripped attachments, embeds and
reactions from the channel, so a scam image could not render in the
seconds before the purge landed.
Two details mattered more than the mechanism. The everyone-role was
rejected as a bypass role, because accepting it would have silently
disarmed the trap while still looking configured. And if the
disclaimer was ever deleted, the bot posted it again — a trap
without its warning is just a channel that kicks people for no
stated reason.
The last feature built, six days before the end. It shipped
working.
04 — The AI chat guard
A system prompt that could not be talked out of the bot
One channel per server ran a chat model, and the obvious game
starts immediately: talk it into printing its own instructions.
Roleplay, a fake “everything above was a test, the real
instructions follow”, a claimed authorised penetration test —
the whole catalogue turns up within a day of switching it on.
Two things answered it, and neither is the prompt asking nicely.
First, the instructions go through the API’s system role
rather than being pasted on top of the user’s message, so the
model renders them as a genuine system turn instead of more text
a user happened to type. “Ignore previous instructions” then has
to beat the role separation itself, not a label in a string.
Second, and this is the part that actually holds: a random token
is generated per process and embedded in the prompt, alongside a
handful of phrases distinctive to it. Every reply is matched
against those before it leaves the bot. It does not matter which
framing talked the model round — the check never looks at the
attack, only at whether instruction text is present in the
output. When it is, the reply is dropped and the user gets a
refusal instead.
A blocked attempt is worth seeing, so a server can name a private
channel for them. The report carries who tried it, where, their
exact prompt, and the model output that was stopped — with
mentions stripped, because a security notice should not be able
to ping the room. The block has already happened by then; the
log exists so somebody can read the attempt afterwards.
What I would defend hardest is the comment sitting above it
in the source: this catches a near-verbatim leak, not a
paraphrase, and a small local model can still be talked into
ignoring its instructions. It is a backstop for the exact
attack class we saw, not a solution to the general problem.
Mitigations that describe themselves accurately are the only
ones anyone can reason about later.
Operations
The tooling nobody using it ever saw.
Neither of these is a feature. They are the two internal things that
made shipping to a live bot survivable.
Off the laptop, into version control
The stack lived where a stack should never live: on a machine,
reaching the server by dragging files across in an FTP client.
One dead drive, one bad overwrite, and a year of work is a
story rather than a codebase. I moved the whole thing, on my
own, into a repository with a real path to production behind it
— line endings pinned so a Windows checkout and a Linux runner
agree on what a file even is, a deploy that ships the last
pushed commit and restarts the process manager, an exclude list
that stops a deploy overwriting the live config and the
server-side runtime state, and a written runbook for the parts
that live outside the repo.
None of that is a feature and no user ever noticed it. It is
the reason the project is still around to be argued over.
Two deploy routes that agreed byte for byte
A GitHub Actions workflow shipped the last pushed commit and
restarted the process manager; the dashboard shipped individual
files straight off a laptop for a fix being tested. The trap is
line endings — a Windows checkout holds CRLF for some files and LF
for others, so the two routes would have overwritten each other’s
copies of unchanged files forever. The dashboard therefore hashed
and uploaded the bytes git would check out, resolved per
file, rather than the bytes on disk. Both routes shared one exclude
list, so neither could ever clobber a live config.
The Command Visualiser
Internal only — no user ever saw this. A generated, self-contained
page that read every command file the bot would register and laid
out its options, subcommands, and the replies each branch could
send, so a command could be reviewed before production rather than
after. It flagged the ones that fail silently from the
wrong account, which is the worst way for a command to be broken,
and listed the files that would not load at all — separating the
ones genuinely broken from the ones merely missing a package on
this laptop.
Handing over
My part ended for reasons that had nothing to do with the code.
The project did not end with it. It is still running, still being
worked on, still doing the job in servers full of people who will
never know which half of it was mine — which is, honestly, the
correct outcome for infrastructure. You do not get to keep the
thing. You get to have built it.
My copy of the history is kept whole — every branch, the full log,
my last commit exactly as it stood. Nothing deleted, nothing
rewritten to look better in hindsight. It was the largest thing I
had built, it went out to real servers for real people, and it
worked.