Skip to content
 
 

Latest commit

 

History

56 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

D5 spam guard bot

A Node.js Discord bot that uses OCR and image hashing to analyze attached images and images shown in Discord link previews. When an image contains a withdrawal keyword and a payout keyword, the bot:

  1. Deletes the message.
  2. Applies a timeout to the author.
  3. Sends a report to the moderation channel.

Invite the bot to your server:

Discord OAuth2 invite

Detection rules

The OCR result must contain both of these keyword groups:

  • Withdrawal group: Withdrawal.
  • Payout group: Success, Succeeded, Successful, Successfully, or USDT in uppercase.

The keywords:

  • Are case-insensitive.
  • Can appear in any order.
  • Can appear on different lines or far apart in the image.
  • Must appear as complete words.

You can tune the detection sensitivity per server with /setup paranoia:

  • low - exact visual hash match only.
  • medium - visual hash match or OCR text containing Withdrawal, Succeeded, and USDT.
  • high - visual hash match or OCR text containing Withdrawal and either Succeeded or USDT.

The default paranoia level is high.

Anti-raid protection

The bot includes an anti-raid protection that detects when a user sends the same message in multiple text channels within less than one minute. When a raid is detected, the bot:

  1. Deletes the repeated messages in the affected channels.
  2. Applies the configured timeout to the user.
  3. Sends an alert to the moderation channel, including the deleted message.

Anti-raid is enabled by default at the high level. Configure it per server with /setup anti-raid, for example:

/setup anti-raid enabled:true level:high

Spam message protection is enabled by default. A matching spam message is deleted and its author is timed out. Known spammer IDs can be added to spam-users.json; messages from those users are handled the same way even if their message does not match a configured spam phrase.

Available levels are:

  • high - triggers when the same message is sent in 3 or more channels.
  • medium - triggers when the same message is sent in 4 or more channels.
  • low - triggers only when the same message is sent in every text channel in the server.

Spam message protection

The bot can also block text messages or embed descriptions that contain an entry from the list in spam-messages.json. Matching ignores capitalization, accents, and repeated spaces. Matching messages are deleted, the author is timed out, and a moderation alert is sent when a moderation channel is configured.

Spam protection is enabled by default. Server administrators can toggle it with:

/setup spam messages enabled:false
/setup spam messages enabled:true

Add or remove entries directly in the messages array in spam-messages.json, one message or phrase per JSON array entry. Multi-line messages can be pasted with their line breaks as-is; the bot accepts and normalizes those line breaks when it loads the file. The descriptionPatterns entries are regular expressions for promotional descriptions. They are ignored when a descriptionExclusions expression detects a negation such as no NSFW or NSFW not allowed. Its status is also shown by /setup status.

Use enabled:false to disable the protection. The anti-raid configuration is stored separately for each server and is shown by /setup status.

Malicious server invite protection

The bot can resolve Discord invitation links and compare the destination server ID with a blocklist. This protection is enabled by default. When a message contains an invitation to a blocked server, the bot deletes the message, applies the configured timeout to the author, and sends a moderation alert. A new invite link to the same server is still blocked because matching uses the destination server ID, not the invite code.

The global blocklist is malicious-servers.json at the repository root. Add one Discord server ID per JSON array entry:

[
  "123456789012345678",
  "987654321098765432"
]

The file is copied into the Docker image. Rebuild/redeploy the image after changing it. The /setup malicious-servers add command can also add a local entry for the current server without modifying the repository file.

Manage it with:

/setup malicious-servers protection enabled:true
/setup malicious-servers add server-id:123456789012345678
/setup malicious-servers remove server-id:123456789012345678
/setup malicious-servers list

The enabled/disabled state and local entries are stored separately for each server and are shown in /setup status. Expired, deleted, or otherwise unavailable invites cannot be resolved and are left untouched.

NSFW server invite protection

The bot can also inspect the name returned for an invitation's destination server. It compares that name against the keywords in nsfw-server-keywords.json, including terms such as NSFW, +18, 18+, 🔞, squirt, sex, porn, xxx, adult, hentai, and onlyfans. Matching is case-insensitive and ignores accents.

This protection is enabled by default and can be disabled per server with:

/setup nsfw-servers protection enabled:false
/setup nsfw-servers protection enabled:true

The keyword file is copied into the Docker image, so rebuild/redeploy the image after changing it. If the invitation has expired, is unavailable, or does not expose a server name, the NSFW-name check is skipped.

Server admins can also:

  • Set a custom timeout with /setup timeout.
  • Exclude roles from detection with /setup excluded-role add, /setup excluded-role remove, and /setup excluded-role list.
  • Toggle whether server administrators are excluded with /setup excluded-administrators enable and /setup excluded-administrators disable.
  • Enable, disable, or change the sensitivity of anti-raid protection with /setup anti-raid.

For example, an image containing Withdrawal near the top and Succeeded near the bottom is considered a match.

The bot scans:

  • Images uploaded directly as Discord attachments.
  • Images and thumbnails displayed in Discord embeds generated from links.
  • Images and embeds contained in forwarded message snapshots.
  • Every image in a multi-image message.

Each image is evaluated independently. If any single image contains a matching withdrawal keyword and payout keyword, the entire outer message is deleted and the user who sent or forwarded it is timed out when Discord allows it.

You can also add a public easter egg response by dropping meme images into easter-egg photos/. The bot builds hash signatures from that folder and, when one of those images is detected, replies in the channel with Jajaja, piqué. in Spanish or Hahaha I got it 😜 in English instead of moderating the message.

Discord may generate a link preview shortly after the original message is created. The bot handles both new-message and message-update events so those delayed previews are scanned as well. A plain link that Discord does not convert into an image embed is not downloaded automatically.

Requirements

  • Node.js 20 or newer.

Discord permissions

OAuth2 scopes

When generating the bot invitation in the Discord Developer Portal, select:

  • bot — adds the bot account to the server.
  • applications.commands — installs the /setup slash command. Discord includes this scope by default when the bot scope is selected, but it should remain enabled.

Bot permissions

Grant the bot these permissions:

Permission Where it is required Purpose
View Channels Monitored channels and the moderation channel Receives new messages and accesses the configured moderation channel.
Manage Messages Monitored channels Deletes messages containing a matching image or raids.
Moderate Members Server-wide Applies the configured timeout to the message author.
Send Messages Moderation channel Sends moderation alerts.
Embed Links Moderation channel Sends the formatted moderation report embed.

The combined permission integer for these five permissions is 1099511655424. It can be entered in an OAuth2 bot invitation as the permissions value.

The bot does not require Administrator, Ban Members, or Kick Members. Granting Administrator is not recommended.

Role hierarchy

The bot's highest role must be above the roles of users it needs to time out. Discord does not allow the bot to time out:

  • The server owner.
  • Members with the Administrator permission.
  • Members whose highest role is equal to or above the bot's highest role.

If the bot cannot apply a timeout because of role hierarchy or permissions, it still attempts to delete the message and records the timeout failure in the moderation alert.

By default, messages from server administrators and the server owner are ignored completely. Use /setup excluded-administrators disable to scan them too. When administrator exclusion is disabled, Discord may still prevent the timeout, but the bot can still delete matching messages when it has permission.

Members with an excluded role are also ignored completely. These exclusions apply to image moderation, anti-raid protection, and spam message protection.

Administrator permissions

The person running /setup moderation-channel, /setup paranoia, or /setup status must have the Manage Server permission. The bot itself does not need Manage Server.

Gateway intent

In the Discord Developer Portal, open the application, go to Bot > Privileged Gateway Intents, and enable Message Content Intent. Discord considers attachments part of message content; without this intent, the bot receives an empty attachment collection.

Installation

pnpm install
cp .env.example .env

Set DISCORD_TOKEN in .env.

Running the bot

pnpm start

The first OCR run may download the English language data and take longer. The worker is reused for subsequent images.

Server configuration

Server administrators with Manage Server can configure the bot with:

  • /setup moderation-channel to choose where alerts are sent.
  • /setup paranoia to set the per-server detection sensitivity.
  • /setup timeout to set the per-server timeout.
  • /setup excluded-role ... to manage ignored roles.
  • /setup excluded-administrators ... to include or exclude server administrators.
  • /setup anti-raid to enable or configure anti-raid protection.
  • /setup status to review the current configuration.

Easter eggs

Store the meme images you want the bot to recognize in easter-egg photos/. Run pnpm build:easter-egg-photos to regenerate generated/easter-egg-photo-manifest.json.

Docker

The included image uses node:22-alpine, installs production dependencies only, and runs the bot as the non-root node user.

Create the environment file before starting the container:

cp .env.example .env

Set DISCORD_TOKEN in .env, then build and start the bot:

docker compose up -d --build

View its logs:

docker compose logs -f bot

Stop the bot:

docker compose down

The Compose configuration creates two named volumes:

  • bot-data stores the per-server moderation channel configuration.
  • ocr-cache stores the downloaded Tesseract English language data.

Both volumes survive container recreation and image upgrades. Running docker compose down -v deletes them, including the saved moderation channel configuration.

Ports

No ports need to be exposed or published. The bot connects outward to the Discord Gateway over HTTPS and WebSocket connections. It does not run an HTTP server or accept inbound network traffic.

The relevant Compose configuration intentionally contains no ports section:

services:
  bot:
    build: .
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - bot-data:/app/data
      - ocr-cache:/app/tessdata

Automatic deployment with GitHub Actions

The workflow in .github/workflows/deploy.yml runs on every push to main and can also be started manually.

It performs these steps:

  1. Installs dependencies and runs the test suite.
  2. Builds the Docker image.
  3. Publishes latest and commit-specific tags to GitHub Container Registry.
  4. Optionally connects to a server over SSH, pulls the exact commit image, and restarts the bot with Docker Compose.

The published image name is:

ghcr.io/dh-555/spam-guard-bot

Server preparation

Install Docker Engine and the Docker Compose plugin on the destination server. Create the deployment directory and its environment file once:

sudo mkdir -p /opt/d5-spam-guard-bot
sudo chown "$USER":"$USER" /opt/d5-spam-guard-bot
cd /opt/d5-spam-guard-bot
nano .env

The server-side .env must contain at least:

DISCORD_TOKEN=your_real_bot_token

GitHub Actions deliberately does not overwrite this file.

The SSH user must be able to run docker and docker compose without an interactive password prompt. No inbound application ports are required; only SSH access is needed for deployment.

GitHub Actions variables

Create these variables under Settings > Secrets and variables > Actions:

Variable Location Required Example
ENABLE_DEPLOY Repository variable Yes true
DEPLOY_PATH Repository or production environment variable No /opt/d5-spam-guard-bot
DEPLOY_PORT Repository or production environment variable No 22

If ENABLE_DEPLOY is not exactly true, the workflow still tests and publishes the image but skips the SSH deployment.

GitHub Actions secrets

Create these secrets:

Secret Purpose
DEPLOY_HOST Server hostname or IP address.
DEPLOY_USER SSH username.
DEPLOY_SSH_KEY Private SSH key used only for deployment.
GHCR_USERNAME GitHub username used by the server to pull the image.
GHCR_PULL_TOKEN Personal access token (classic) with read:packages.

The corresponding public SSH key must be added to ~/.ssh/authorized_keys for DEPLOY_USER.

For a private GHCR package, GHCR_PULL_TOKEN needs permission to read packages. If the package is made public, server-side registry authentication can be removed from the workflow.

Legal pages and GitHub Pages

The docs/ directory contains a static legal site with:

  • Privacy Policy: docs/privacy.html
  • Terms of Service: docs/terms.html
  • Legal landing page: docs/index.html

To publish it with GitHub Pages:

  1. Open the repository on GitHub.
  2. Go to Settings > Pages.
  3. Under Build and deployment, select Deploy from a branch.
  4. Select the main branch and the /docs folder.
  5. Save the configuration.

The expected URLs are:

https://dh-555.github.io/Anti-Mr-Scam-bot/
https://dh-555.github.io/Anti-Mr-Scam-bot/privacy.html
https://dh-555.github.io/Anti-Mr-Scam-bot/terms.html

The included policies are project-specific templates, not legal advice. The person or organization operating the Bot should review them for the applicable jurisdiction and deployment practices.

Discord setup

After starting the bot, a server administrator can use Discord's native slash command interface:

/setup moderation-channel channel:#moderation

Discord displays a channel picker for the channel option. The selection is saved separately for each server and persists across restarts in data/settings.json.

Use /setup status to view the currently configured moderation channel. The commands require the Manage Server permission and their responses are only visible to the administrator who runs them.

If a moderation channel has not been configured yet, the bot still scans and moderates matching images. In that case, it deletes the message, applies the timeout when possible, and posts a short notice in the same channel telling admins to configure /setup moderation-channel for full alerts and details.

Configuration

Variable Required Default
DISCORD_TOKEN Yes
TIMEOUT_MINUTES No 1440 (24 hours)
MAX_IMAGE_SIZE_MB No 8
MAX_IMAGE_PIXELS No 16000000
IMAGE_DOWNLOAD_TIMEOUT_MS No 15000
OCR_CACHE_PATH No tessdata
OCR_EFFORT No high
VISUAL_REFERENCE_MANIFEST_PATH No generated/visual-reference-manifest.json
VISUAL_MATCH_THRESHOLD No 6

OCR_EFFORT can be low, medium, or high. Higher effort tries more preprocessing passes and crops, which improves blurry screen photos at the cost of slower OCR.

Reference images live in the repository under visual-references/. The build step hashes them with a perceptual hash and writes the manifest to generated/visual-reference-manifest.json. At runtime, the bot reads only that manifest. Lower thresholds are stricter; 0 means exact hash equality.

To regenerate the manifest locally:

pnpm build:visual-references

With the current reference folder, startup logs should include:

[Visual matching] Loaded 19 reference hash(es).

Discord limits timeouts to a maximum of 28 days.

Tests

pnpm test

About

Discord bot created by D5 to catch and remove Mr Beast phishing messages

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages