Trap channel
Set up a channel nobody should ever post in, and punish the self-bots and spam accounts that do.
A trap channel is a channel with a standing warning in it and nothing else. Real members read the warning and leave it alone. Automated accounts that post into every channel the moment they join do not, and get punished immediately. It costs nothing to run and catches the kind of account that arrives faster than a moderator can react. Every trap command needs the Manage Guild permission.
Set the trap
Create it
Run ,trap setup to have the bot make a channel for
you, or name one you already have. Creating one needs Manage Channels.
Syntax
,trap setup [#channel]
Example
,trap setup #do-not-post
Choose the punishment
Run ,trap action. The default is softban.
Syntax
,trap action <softban|ban|kick|timeout|mute|jail> [duration]
Example
,trap action timeout 2h
Whitelist anyone who must be able to post there
Run ,trap whitelist add.
Syntax
,trap whitelist <add|remove|list> [@role|@user]
Example
,trap whitelist add @Staff
Check it
Run ,trap settings to see the whole configuration
in one embed.
Syntax
,trap settings
Leave the channel visible. A trap nobody can see is a trap nothing walks into. The bot posts a standing embed in it saying not to send messages, so a member who wanders in has been told.
Threads inside a trap channel count as part of the trap. A message posted in one is treated the same as a message in the channel itself.
Choose what happens to whoever posts
,trap action sets the punishment, and a duration with
it: ,trap action timeout 2h.
| Action | Bot needs | Effect |
|---|---|---|
softban | Ban Members | Bans with a message purge, then immediately unbans. The default |
ban | Ban Members | Permanent |
kick | Kick Members | They can rejoin |
timeout | Moderate Members | Reversible. Takes an optional duration, one hour by default |
mute | Manage Roles | Needs a mute role set with ,config muterole |
jail | Manage Roles | Needs a jail role set with ,config jailrole |
Every catch is deleted before anything else happens, and the caught message never reaches your snipe
cache or your delete and edit logs. One trap embed goes to the log channel from
,config logchannel.
Staff are not immune
Only the server owner and whitelisted users and roles are immune. An Administrator who is not whitelisted gets caught and punished like anybody else. That is deliberate: permission-based immunity made the trap a silent no-op for every moderator who tried to test it.
It also means that unless you are the server owner or whitelisted, your own ,trap commands are
deleted inside a trap channel before they can be read as commands. Configure it from somewhere
else, or whitelist your staff role.
Punish repeat offenders harder
Escalate a second catch
,trap escalation applies a harsher punishment to
anybody caught more than once. It is off by default and escalates to ban unless you name something
else.
Syntax
,trap escalation <on|off> [action]
Example
,trap escalation on ban
Offence counts come from a member's punishment history, so clearing their history with
,punishmenthistory clear resets
their count.
Treat a new account's first catch as a second
,trap joingate applies the escalation action on a
member's first catch when their account or their membership is newer than the number of days you
set. It accepts 1 to 90 days and defaults to 7, and it is off by default.
Syntax
,trap joingate <on|off> [days]
Example
,trap joingate on 7
The join gate uses the escalation action whether or not escalation itself is on, and that action is
ban until you change it. ,trap escalation sets it
either way, so ,trap escalation off timeout gives the gate a gentler action while leaving repeat
escalation switched off.
Manage the warning embed
The standing embed in a trap channel is controlled by two separate commands, and they do different things.
Show or hide it with trap counter
,trap counter controls whether the embed exists at all
and what number it shows.
Syntax
,trap counter <on|off|reset|set> [count]
Example
,trap counter set 42
| Argument | Does |
|---|---|
on / off | Shows or hides the embed. off gives you a silent trap with nothing posted in it |
reset | Sets the displayed count back to zero |
set <count> | Corrects the displayed count |
Restyle it with trap embed
,trap embed controls what it looks like.
Syntax
,trap embed <view|set|remove|resend> [code]
Example
,trap embed set {title: keep out}$p{field: Caught && **{trap.counter}** && inline}| Argument | Does |
|---|---|
view | Returns the current embed code, or the default one if you have not set anything |
set <code> | Replaces it |
remove | Goes back to the default |
resend | Deletes the current embed and posts a fresh one |
Turning the counter off does not delete your custom code, and setting a code does not turn the counter on.
Start from the code the bot already posts
The default warning embed is itself an embed code, and
,trap embed view hands back the one your server is
running. Here it is, ready to paste and edit:
Syntax
,trap embed set <code>
Example
,trap embed set {title: 🪤 DO NOT SEND MESSAGES IN THIS CHANNEL}$p{color: #FF4747}$p{description: This channel is a trap for spam bots. Anyone who posts here is punished automatically — no warning.}$p{field: ⚖️ Punishment && **{trap.action}** && inline}$p{field: 🤖 Caught && **{trap.counter}** && inline}$p{footer: {guild.name} • whitelisted roles are exempt && {guild.icon}}A shorter one that says the same thing in a description instead of fields:
Syntax
,trap embed set <code>
Example
,trap embed set {color: #FF4747}$p{title: 🪤 Do not post here}$p{description: This channel is a trap. Anyone who posts is punished automatically — **{trap.action}**, no warning. Caught so far: **{trap.counter}**.}$p{footer: {guild.name} && {guild.icon}}`{user}` is the bot on this embed
Nobody triggered the warning embed (it just stands there), so there is no member for {user},
{user.mention} or {user.avatar} to mean, and the bot fills in for all three. A code that greets
a member here greets the bot. Save those tokens for the catch DM, which has a real person.
Two more things worth copying out of those codes:
- Add the escalation field only when escalation is on.
{trap.escalation}becomes an empty string while,trap escalationis off, and the field is posted anyway.{field: 🔁 If repeated && **{trap.escalation}** && inline}therefore leaves the bold markers with nothing between them: a field whose value is four literal asterisks,****. Without the bold it is quieter and no better: an empty value is padded out to an invisible character, so you get a heading with nothing under it. The default code adds this field only once escalation is on, which is why you never see either. - The counter is an embed and only an embed. Every embed tag works:
{title:},{description:},{field:},{color:},{footer:},{author:},{url:},{image:},{thumbnail:}and{timestamp:}. What is missing is everything that renders outside an embed:{v2:}is refused when you save, and{content:},{button:},{gallery:},{section:}and{separator:}produce nothing at all. Every code is rendered once before it saves, so one that produces no embed is rejected rather than quietly leaving the channel unwarned.
The four trap-only tokens, in full. Everything else in a counter code is an ordinary variable:
| Token | Becomes |
|---|---|
{trap.counter} | how many accounts the trap has caught |
{trap.action} | the punishment, written out |
{trap.channel} | the trap channel's name, written #name |
{trap.escalation} | the escalation punishment, or nothing when escalation is off |
A standing warning has no audience until someone walks into it, so write it for the member who wandered in, not for the bot that stands in for them.
Tell the person what happened
,trap dm controls the message a caught account receives. It
is on by default.
Syntax
,trap dm <on|off> | invite <on|off> | embed <code|check|reset>
Example
,trap dm invite on
| Argument | Does |
|---|---|
on / off | Sends or does not send a DM |
invite <on/off> | Adds a single-use rejoin invite, valid for a day, to a channel that is not the trap (in softban and kick DMs only) |
embed <code> | Replaces the default DM with your own embed code |
embed check | Shows the code currently set |
embed reset | Goes back to the default DM |
The DM is sent before a ban or kick, which is the only moment it can still be delivered.
Write the DM they actually get
Syntax
,trap dm embed <code>
Example
,trap dm embed {color: #FFA500}$p{title: 🪤 You posted in a trap channel}$p{description: {user} — you sent a message in {trap.channel} on {guild.name}, so you were **{action}**. If you are a real person, your account may be compromised: change your password and turn on 2FA.}$p{footer: {guild.name} && {guild.icon}}$p{button: link && Appeal && https://discord.com}The bot saves the code and posts the rendered result back. Three things worth copying out of it:
{user}is the caught account here. This is the one trap surface with a real person behind it, so{user}renders their tag, the opposite of the warning embed above. One feature, two surfaces, one token meaning two different things.{action}is a whole phrase, not one word. A softban expands to "softbanned (banned and immediately unbanned, your recent messages were removed)", so writeyou were **{action}**and let it finish the sentence. It also names the punishment that landed, not the one you configured: a catch that escalated says "banned".- The counter tokens do not reach a DM. Only
{action}and{trap.channel}are filled in here;{trap.counter},{trap.action}and{trap.escalation}belong to the warning embed. An unresolved token does not go blank:{trap.counter}in a DM posts the literal texttrap.counter.
The preview is not the DM
The message the bot posts back when you save proves your code parses. It does not show you what a caught account receives, because it fills the same tokens from where you are standing:
{action}previews as the bare word from,trap action:softban. The real DM uses the full phrase, and names the action actually applied, so an escalated catch saysbannedwhere your preview saidsoftban.{trap.channel}previews as your primary trap channel, always. The real DM names the channel they actually posted in, and a thread resolves to the channel above it.{user}previews as you, the moderator who ran the command. The real DM has the caught account.- The rejoin invite is not in the preview at all. It is built at catch time and appended then.
Write the sentence so it reads correctly for the longest of those, not the shortest. Only a real catch renders the real DM.
Unlike the warning embed, a DM code may carry plain text, buttons and Components V2 as well as an
embed. ++delete is accepted and then ignored: deleting a DM the member may never have opened takes
away the only record of why they were punished.
The rejoin invite reaches `softban` and `kick` DMs only
Those are the two punishments that put somebody outside the server and let them walk back in, so
they are the only two the invite is built for. Under ban, timeout, mute or jail,
,trap dm invite on saves happily and does nothing: no invite is created and nothing is added to
the DM. The bot also needs somewhere to point it (a channel that is not a trap, where it may
create an invite), so a softban DM can go out without a line even with the setting on.
When it does appear it is appended to whatever your code rendered, so never write one yourself.
The DM is the only part of the trap a caught account can still read once it is gone, so tell them what happened and where to appeal.
Run more than one trap
Add more trap channels
,trap channels takes add, remove and list, up to
five channels. Each gets its own warning embed. The channel you named during setup is the primary one
and cannot be removed: re-run ,trap setup to change it,
which resets the list.
Syntax
,trap channels <add|remove|list> [#channel]
Example
,trap channels add #do-not-post-2
The catch count is shared across the whole server, so a catch anywhere updates every trap channel's embed.
Rename them on a schedule
,trap randomize periodically renames your trap
channels so a fixed name cannot be learned and skipped. It accepts 1 to 168 hours, defaults to 24, is
off by default, and needs Manage Channels. Channels the bot cannot rename are left alone.
Syntax
,trap randomize <on|off> [hours]
Example
,trap randomize on 24
Review and remove
,trap settings prints the whole configuration.
,trap stats reports what the trap has actually caught:
totals by action, unique accounts, how many were repeats, and the busiest days.
,trap disable removes the trap.
Syntax
,trap stats
Syntax
,trap disable
Common issues
A moderator got banned by the trap. Whitelist them or their role with
,trap whitelist add. Permissions never grant
immunity.
My ,trap command did nothing. You ran it inside a trap channel without being the server owner
or whitelisted, so the message was deleted before it could be read as a command. Run it somewhere
else, or whitelist yourself.
Someone posted in the trap and nothing happened. The bot could not apply the punishment: usually a missing permission, a member above it in the role hierarchy, or a mute or jail role that is not set. It reports the failure rather than recording a catch that never happened, but it reports it to your log channel, so with no log channel set, a blocked catch looks exactly like nothing happening at all. The message is still deleted either way. Set a log channel first, then re-test.
Nothing is logged. Set a log channel with
,config logchannel. With nowhere to deliver
to, the bot does not warn you that a report had no destination. It simply posts nothing, which is
what makes the entry above so easy to misread. Set this before you test anything.
The warning embed disappeared. Run
,trap embed resend.
My custom embed was refused. A counter embed has to be an actual embed. A code containing only text or only buttons cannot be posted as a standing message.
The tags, variables and buttons themselves are one language shared by every surface that takes a code. Embed codes is where it is explained.