Trap-channel enforcement and automatic attachment-spam detection for Discord.
Spam Catcher is a Discord moderation bot with two independent protection modes:
- Trap Channels apply a configured timeout or ban action when a user posts in a designated channel.
- Automatic Attachment Detection watches non-trap channels for repeated attachment bursts from the same user.
Both modes use the same timeout DMs, appeal flow, timeout-removal DMs, and ban DMs. Guild settings are stored in PostgreSQL. New guilds remain disabled until an Administrator completes setup.
Invite Spam Catcher to your Discord server and try every feature without installing or hosting anything yourself.
Included features:
- Trap Channels
- Automatic Attachment Detection
- AI Verdict image analysis (default:
3checks per server each day) - Timeout, ban, and appeal workflows
- Trap Channel Notices
- English and Indonesian localization
Run Spam Catcher with your own Discord app and PostgreSQL database.
Important
Spam Catcher does not read or analyze message text.
Discord's Message Content Intent is used only to receive attachment metadata. Automatic Attachment Detection uses the author, channel, attachment count, timestamps, and, when AI Verdict is enabled, the first supported image attachment.
| Feature | Description |
|---|---|
| π§ Trap Channels | Apply a timeout or ban when a user posts in a designated channel. |
| π Automatic Attachment Detection | Detect repeated attachment bursts, create one tracked Danger incident, and let Administrators delete its source messages from the review card. |
| π§ AI Verdict | Analyze the trigger image once and add OCR-based evidence to the existing incident. |
| βοΈ Moderation And Appeals | Share timeout, ban, DM, appeal, and administrator review workflows. |
| π£ Trap Channel Notices | Post and maintain warning messages in configured trap channels. |
π§ Trap Channels
Administrators choose one or more text channels as traps and configure the moderation action. When a non-administrator posts in an enabled trap channel, Spam Catcher records the event and applies the selected timeout or ban flow.
Trap Channels and Automatic Attachment Detection have separate enable switches. Messages in active trap channels are excluded from Automatic Attachment Detection, preventing both systems from acting on the same message.
Caught messages remain in the trap channel. Event totals count incidents, not distinct users.
π Automatic Attachment Detection
Automatic Attachment Detection is disabled by default. When enabled, it processes messages from users the bot can moderate and ignores:
- bots and webhooks
- Discord Administrators
- users above the bot in the role hierarchy
- users the bot cannot moderate because of missing permissions
- messages in active trap channels
Detection follows one fixed window:
- A message with
2+attachments starts a10 minuteAlert window. - The next message from the same user with
2+attachments inside that window creates one Danger incident. - The bot immediately marks the user as a spammer, increments their incident count once, applies the timeout, sends the timeout DM, and posts one actionable Danger card to the Review Channel. Each lifecycle transition is also recorded as a separate compact Log entry.
- The Danger card provides a Delete Evidence button for Administrators. Nothing is deleted automatically, and configured trap-channel messages are always preserved.
- Later qualifying messages inside the same window update the original incident with affected channels, message and attachment totals, and latest activity. They do not repeat moderation or AI analysis.
- After the window expires, the next qualifying message starts a new Alert window.
Same-channel and cross-channel repeats both create Danger incidents. Removing the timeout, successfully banning the user, or finding that the member is no longer in the guild closes the active window. If the user can send messages in the guild again, their next qualifying message starts a new Alert window.
After Danger is confirmed, the shared moderation policy:
- sets the active spammer flag
- increments the user's stored incident count once
- applies the configured Automatic Detection timeout, which defaults to
28 days, unless immediate-ban mode is selected - schedules or immediately executes Auto Ban when enabled
- posts one actionable Danger card in the configured Review Channel and compact audit entries in the Log Channel
- sends the matching timeout/appeal or ban DM if Discord accepts the DM
Danger cards in the Review Channel display moderation and AI statuses separately. When an incident is resolved, the Review card becomes a compact summary instead of being deleted. The Log Channel receives short append-only records for alerts, detection, moderation, AI, follow-ups, appeals, evidence operations, and administrator actions; it never receives a copy of the Review card.
π§ AI Verdict
AI Verdict is an optional Automatic Attachment Detection add-on. It never runs for Trap Channel events.
When enabled, AI Verdict runs once after Danger is confirmed and the shared moderation policy has started. It updates the existing Review card with its result but never delays, removes, or changes the timeout or ban.
AI Verdict behavior:
- Requires
OPENROUTER_API_KEYorGEMINI_API_KEY. - Uses OpenRouter when configured; Gemini is used only when OpenRouter is not configured.
- Defaults to OpenRouter model
xiaomi/mimo-v2.5or Gemini modelgemini-2.5-flash. These were chosen as the cheapest suitable image-capable options at the time of selection, not as requirements. You can replace either with any compatible model supported by that provider. - Analyzes only the first supported image from the message that triggered evaluation.
- Returns a caption, OCR matches, and confidence score.
- Matches OCR text against the guild's configured trigger words.
- Defaults to a confidence threshold of
0.7. - Runs through a per-guild queue with at most
2concurrent analyses.
The default quota is 3 verdicts per guild per day. The quota resets using the guild's configured timezone, which defaults to UTC. The setup panel displays the current usage, and the first quota-counted verdict on a new guild-local date sends a reset notice to the Log Channel.
OpenRouter image URL, base64 fallback, and JSON retry attempts count as one bot verdict. If every failed attempt is explicitly unbilled, the bot refunds that event's quota slot. Explicitly unbilled means OpenRouter reports usage.cost = 0, reports zero completion tokens with an error or no finish reason, or rejects the request before generation. Billed or unknown malformed responses remain counted.
If the quota is exhausted, no provider request is made. Moderation is unaffected, and the existing Review card records the quota result. Guild IDs in AI_VISION_DAILY_LIMIT_BYPASS_GUILD_IDS bypass only this daily quota.
βοΈ Moderation And Appeals
Trap Channel incidents support these timeout durations:
10 Minutes30 Minutes1 Hour6 Hours12 Hours1 Day3 Days7 Days14 Days28 Days
Shared Auto Ban modes for Trap Channels and Automatic Detection:
Auto Ban Off: apply the timeout without scheduling a banBan After Appeal Window: apply the timeout, then ban after the selected appeal periodBan Immediately: ban without applying a timeoutBan After Timeout Ends: apply the timeout, then ban when it expires
Appeal window options:
10 Minutes30 Minutes1 Hour2 Hours6 Hours12 Hours24 Hours
Timed-out users receive a DM with an Appeal button. The button opens the shared appeal modal, where the user submits an explanation. The explanation is stored with the incident and displayed on its moderation card.
Administrator card actions:
Remove Timeout: removes the timeout, resets the active spammer flag, closes the detection window, and marks the incident resolvedBan User: bans the user and, after a successful ban, resets the active spammer flag, closes the detection window, and marks the incident resolved
When an Administrator removes a timeout, the bot DMs the user that the timeout was lifted and records whether Discord accepted the DM. Timeout removal always calls Discord's member.timeout(null, ...) when the member exists instead of relying on cached timeout fields.
If a user has left, been kicked, or been banned before an Administrator acts, the bot records that the member is unavailable and updates the existing card without crashing.
π£ Trap Channel Notices
Trap notices warn users not to post in configured trap channels. Administrators can post or update them from /spam-catcher setup.
Notice message IDs are stored in spam_catcher_notice_messages. The bot edits a stored notice when the message still exists and is editable. Otherwise, it posts a replacement and stores the new message ID.
Requirements:
- Node.js
18+ - npm
- PostgreSQL reachable through
DATABASE_URL - a Discord application with a bot user
Install dependencies and verify JavaScript syntax:
npm install
npm run check- Create an application in the Discord Developer Portal.
- Create its bot user and copy the token.
- Enable
Message Content Intentunder privileged gateway intents. - Enable the bot in the application's installation settings.
- Invite it with the
botandapplications.commandsscopes. - Grant
View Channels,Send Messages,Read Message History, andModerate Members. - Grant
Ban Membersif you plan to use Auto Ban or theBan Usercard action.
Create .env from the example file:
cp .env.example .envRequired values:
DISCORD_TOKEN=your_discord_bot_token
DATABASE_URL=postgresql://spamcatcher:password@127.0.0.1:5432/spam_catcherOptional runtime values:
PG_SSL_MODE=disable
BOT_POSTGRES_POOL_MAX=2
ALLOWED_GUILD_IDS=
OPENROUTER_API_KEY=
OPENROUTER_MODEL=xiaomi/mimo-v2.5
GEMINI_API_KEY=
GEMINI_MODEL=gemini-2.5-flash
AI_VISION_DAILY_LIMIT_BYPASS_GUILD_IDS=
SUPER_ADMIN_USER_IDS=The model values above are low-cost defaults. Self-hosters can replace them with any compatible image-capable OpenRouter or Gemini model.
PG_SSL_MODE: set todisablefor PostgreSQL on local/VPS127.0.0.1; remote connections require SSL by default.BOT_POSTGRES_POOL_MAX: maximum PostgreSQL pool size; defaults to2.ALLOWED_GUILD_IDS: comma-separated guild allowlist. When empty, any guild may configure the bot, but both moderation features remain disabled until enabled through setup.OPENROUTER_API_KEY: enables OpenRouter for AI Verdict.OPENROUTER_MODEL: defaults to the low-costxiaomi/mimo-v2.5; set it to any compatible image-capable model available through OpenRouter.GEMINI_API_KEY: enables Gemini when OpenRouter is not configured.GEMINI_MODEL: defaults to the low-costgemini-2.5-flash; set it to any compatible Gemini model that accepts image input.AI_VISION_DAILY_LIMIT_BYPASS_GUILD_IDS: comma-separated guild IDs that bypass only the AI Verdict daily quota.SUPER_ADMIN_USER_IDS: comma-separated Discord user IDs allowed to use the owner-only/spam-admincommand in bot DMs. The command is not registered when this value is empty.
Guild IDs, channel IDs, timeout settings, ban settings, language, timezone, trigger words, and daily limits belong in PostgreSQL guild config, not .env.
Follow the beginner guide for Supabase, other PostgreSQL cloud providers, or PostgreSQL on an Ubuntu/Debian VPS.
Tables are created automatically at runtime. You can also apply scripts/schema-postgres.sql manually.
Pending Automatic Detection alert windows and their editable log-message references are stored in automatic_spam_detection_users; confirmed windows copy the reference to automatic_spam_detection_events. This allows no-spam and danger progress to recover after a bot restart.
Required tables:
spam_catcher_configspam_catcher_eventsspam_catcher_notice_messagesautomatic_spam_detection_usersautomatic_spam_detection_eventsautomatic_spam_detection_event_messagesautomatic_spam_detection_evidence_messagesautomatic_spam_detection_ai_usageautomatic_spam_detection_ai_usage_reservationsai_vision_daily_limit_bypass_guilds
Recommended local/VPS connection:
DATABASE_URL=postgresql://spamcatcher:password@127.0.0.1:5432/spam_catcher
PG_SSL_MODE=disableDo not expose PostgreSQL publicly. Bind it to 127.0.0.1 or use a private network.
Start the bot:
npm startOn startup, the bot:
- logs in to Discord
- creates missing PostgreSQL tables
- refreshes the global
/spam-catcherapplication command - starts the delayed-ban loop
- starts Automatic Attachment Detection for guilds where it is enabled
Global Discord command updates may take time to propagate.
Run this command as a Discord Administrator:
/spam-catcher setup
New guilds are disabled by default. Setup is split into two required stages:
- Select separate Log and Review Channels. Stage 2 remains locked until the bot can view and send messages in both.
- Configure Trap Channels, Automatic Detection, AI Verdict, and the shared Auto Ban policy.
Existing guilds with valid, separate saved Log and Review Channels are migrated directly to Stage 2. If either required channel is deleted, inaccessible, or set to the same destination, protection is disabled and setup returns to Stage 1. Saved channels are preselected and changes are stored immediately.
Every saved setup change posts an action-free audit record to the Log Channel with the Administrator and the previous and new values. Changing the Log Channel writes the record to both the old and new destinations; resetting setup writes the record to the previous Log Channel.
Opens the Discord Components V2 setup dashboard for Administrators. Stage 1 verifies required channels. Stage 2 is a summary dashboard for Trap Channels, Automatic Detection with AI Verdict, and shared Auto Ban in that order. Each Open Settings button replaces the same ephemeral message with one focused settings panel and a Back to Dashboard button.
- Purpose: keep informational records separate from cards that require moderator action.
- Controls: select one Log Channel for action-free records and one different Review Channel for moderation cards and appeals.
- Gate: Stage 2 unlocks only after both channels are different, exist, and allow the bot to view and send messages.
[Screenshot]
- Purpose: choose where Trap Channel incidents happen.
- Controls: select trap channels, choose the Trap timeout, enable protection, and post/update notices.
- Purpose: detect repeated attachment bursts outside active Trap Channels.
- Controls: enable detection and configure the optional AI Verdict gate.
- AI behavior: moderation starts immediately. AI Verdict runs afterward and updates the Review card without delaying or changing the timeout or ban.
- Purpose: decide whether and when incidents from either protection system become bans.
- Controls: enable or disable Auto Ban, choose one of three ban modes, and set the appeal window when required.
- Scope: applies to both Trap Channels and Automatic Detection using each source's configured timeout duration.
- Off state: Auto Ban Off keeps timeout-only moderation.
- Applies the configured timeout first.
- Bans after the selected appeal window unless an Administrator resolves the incident.
- Bans as soon as the Trap Channel incident is created.
- Skips the timeout and appeal window.
- Applies the configured timeout first.
- Bans when that timeout expires.
- Purpose: detect repeated attachment bursts outside active Trap Channels.
- Controls: enable or disable detection and review its attachment threshold, fixed window, timeout, Review destination, and Log destination.
- Different from: works independently from Trap Channels and never processes messages inside an active trap.
- Purpose: add optional image analysis to an Automatic Detection incident after moderation starts.
- Controls: enable or disable AI Verdict, edit trigger words, and review confidence, quota, timezone, and provider readiness.
- Behavior: the shared moderation policy applies immediately. AI match, no match, low confidence, provider failure, unsupported image, and exhausted quota update the existing Review card only.
- Purpose: warn users before they post in configured Trap Channels.
- Controls: post or refresh the localized warning message in every configured trap.
- Different from: manages informational channel messages, not moderation behavior.
- Unfortunately, the trap notice message cannot currently be edited. Iβm still working on its Markdown editor.
Sets the guild's interface language:
/spam-catcher lang language: English
/spam-catcher lang language: Indonesia
The selected language applies to setup panels, trap notices, Danger cards, timeout DMs, appeal modals, and shared moderation messages. Supported stored values are en and id.
Returns the guild to Stage 1 after confirmation. It clears setup channels and feature settings, disables protection, closes active Automatic Detection windows, and cancels scheduled bans. Historical incidents and existing Discord messages are preserved.
Shows a user's Automatic Attachment Detection status and Trap Channel event history. The user argument accepts either an @mention or a raw Discord user ID.
/spam-catcher check user:@User
Owner-only controls for the hosted bot. The command uses Discord's user-install context, so it is hidden from users who only share a server with the bot. Enable User Install with the applications.commands scope in the Discord Developer Portal, install the app to each Super Admin's account, set SUPER_ADMIN_USER_IDS in .env, and restart the bot. Then use these commands in a DM with the bot:
/spam-admin guilds: list every guild currently connected to the bot/spam-admin bypass-add guild_id:<id>: bypass the AI Verdict daily quota for a guild/spam-admin bypass-remove guild_id:<id>: disable the bypass, including an ENV default/spam-admin bypass-list: list all effective ENV and DM-configured bypasses/spam-admin quota-reset guild_id:<id>: reset the current guild-local day's AI usage to zero/spam-admin quota-set guild_id:<id> limit:<number>: set the guild's daily AI limit from0to10,000/spam-admin user-reset guild_id:<id> user_id:<id> scope:<active|full>: clear active state or permanently delete that user's stored history in the guild/spam-admin guild-reset guild_id:<id>: restore guild settings to disabled defaults while preserving incidents, already scheduled incident actions, notices, usage, and bypasses
User and guild resets require button confirmation. Database resets do not remove an existing Discord timeout or ban. A full user reset retains only the minimal message references needed for Delete Evidence on an existing Discord card. Every interaction checks the Discord user ID against SUPER_ADMIN_USER_IDS, even when the account has installed the app.
The Discord setup dashboard is preferred. CLI commands require the runtime environment variables because they connect to PostgreSQL and, where applicable, Discord.
Create or update guild config:
npm run config:upsert -- \
--guild-id YOUR_GUILD_ID \
--log-channel-id ADMIN_LOG_CHANNEL_ID \
--review-channel-id ADMIN_REVIEW_CHANNEL_ID \
--trap-channel-ids TRAP_CHANNEL_ID_1,TRAP_CHANNEL_ID_2 \
--timeout-minutes 60 \
--auto-ban false \
--ban-mode delayed \
--ban-delay-minutes 10 \
--language en \
--timezone UTC \
--ai-vision-daily-limit 3Timezone values must be valid IANA names such as UTC, Asia/Jakarta, or America/New_York.
List guild configs:
npm run config:listPost or update notices for one guild:
npm run post:notices -- --guild-id YOUR_GUILD_IDPost or update notices for every enabled guild:
npm run post:notices -- --allNotice failures are logged with the [spam-catcher-setup] prefix. Logs include the channel ID, delivery method, stored message ID, failure stage, Discord response body when available, and final success/failure totals.
Who sees each step, what happens, and when.
- Default trigger: one message with
2+attachments. - Default timer: opens a
10-minuteAlert window for that user. - Action: records the activity; no moderation yet.
[Screenshot]
- Condition: the same user sends another
2+attachment message before the Alert window expires. - AI disabled: confirms one Danger incident and applies the shared moderation policy.
- AI enabled: confirms Danger and applies moderation immediately, then evaluates the first supported trigger image in the background.
- AI result: match, no match, low confidence, analysis failure, unsupported image, or exhausted quota updates the existing Review card without changing moderation.
- Follow-ups: later qualifying messages update the same incident without repeating moderation.
- Review Channel: receives the actionable Danger card with the available moderation and evidence controls.
- Log Channel: receives compact append-only records for every lifecycle and administrator action, but never the full Danger or Summary card.
- Action: applies the configured timeout after Danger is confirmed when the shared policy uses a timeout. Immediate-ban mode skips this step.
- Default duration:
28 daysfor Automatic Detection. - Appeal: the user can explain a mistake; an Administrator can review it and remove the timeout.
- Shared Auto Ban: applies to Trap and Automatic Detection incidents immediately, when the source-specific timeout ends, or after the appeal window.
- Manual action: Administrators can still confirm Ban User from actionable incident cards.
- Trap defaults:
60-minutetimeout and10-minuteappeal window; both are configurable.
- Availability: immediate when AI Verdict is off, or after its analysis finishes when enabled.
- Action: deletes stored Alert, trigger, and follow-up messages outside protected trap channels.
- Result: completion records the Administrator and disables the button; failures remain retryable.
Required Discord permissions:
View ChannelsSend MessagesRead Message HistoryManage MessagesModerate Members
Optional permission:
Ban Membersfor Auto Ban and theBan Usercard action
Required gateway intents:
GuildsGuildMessages- privileged
MessageContent
Manage Messages is required to delete Automatic Attachment Detection evidence outside trap channels. Moderate Members is required for timeout and timeout removal. MessageContent is required for Discord to include attachment metadata; the bot does not inspect message text.
Run the bot under a process manager such as systemd, pm2, or Docker. Keep PostgreSQL on 127.0.0.1 or a private network and use PG_SSL_MODE=disable only for a trusted local/VPS connection.
The delayed-ban loop checks due work every 30 seconds. Restarting the process does not remove stored guild config, incidents, scheduled ban state, evidence references, notice message IDs, or AI quota usage because they are persisted in PostgreSQL.
- Discord Administrators are ignored by both moderation features.
- Caught Trap Channel messages are not deleted.
- Automatic Attachment Detection never deletes evidence automatically. Administrators can use Delete Evidence on the incident card after AI Verdict finishes; configured trap-channel messages are always preserved. A completed deletion records the Administrator in Moderation State and disables the button, while failed deletions remain retryable.
- Caught totals are incident counts, not distinct-user counts.
- Review cards allow Administrators to remove a timeout or ban the user.
- Automatic Detection action controls are posted only in the Review Channel. Log records are separately built, compact, append-only, and contain no moderation controls.
- Successfully resolved Automatic Attachment Detection cards collapse to Incident and Moderation State summaries. The summary identifies the resolving Administrator and, when applicable, the evidence-deletion Administrator without showing timeout attribution; Administrators can use Show Details for the full read-only ephemeral record.
- Removing a timeout cancels the scheduled Spam Catcher ban for that incident.
- Missing members are treated as expected leave, kick, or ban cases instead of runtime errors.
- Automatic Attachment Detection never processes messages in active trap channels.
- AI Verdict failure, low confidence, no match, or quota exhaustion does not delay or change the timeout or ban already applied by Automatic Detection.
- Reworked setup into required-channel and protection-settings stages, with automatic migration and
/spam-catcher reset-setup. - Unified Auto Ban behavior across Trap Channels and Automatic Detection.
- Restored immediate Automatic Detection moderation with AI Verdict running afterward as a background Review-card update.
- Routed active Automatic Detection moderation cards only to the Review Channel and completed outcomes to the Log Channel.
-
Added an Administrator-only Delete Evidence incident-card button that preserves configured trap-channel messages.
-
Added owner-only
/spam-admincontrols through bot DMs for connected-guild listing, persistent quota bypasses, quota resets and limits, user database resets, and guild settings resets.
- Automatic Attachment Detection now ignores active trap channels; the first qualifying attachment message opens an Alert window and the second creates one Danger incident.
- Later qualifying attachment messages update the original Danger incident and card instead of repeating moderation.
- Clearing a user closes the current window while preserving prior incident history, allowing future spam to start a new detection cycle.
- AI Verdict now runs after immediate moderation, checks the first supported image from the trigger message once, and updates the existing Danger card.
- Explicitly unbilled OpenRouter failures no longer consume a guild's daily AI Verdict quota.
- Danger cards now separate moderation and AI statuses, the appeal prompt and button appear at the bottom of timeout DMs, and setup/card text was updated in English and Indonesian.