Skip to content
Moeijiro
← All projects

Case study · Discord bot · engine · dashboard

NexusGuard

Security and moderation bot for Discord servers that stops raids and spam, and explains every action it takes.

Detection, rule and action engines run behind per-server safety caps, so a bad rule can't kick half the server.

Discord SystemsSecurity & Monitoring
Type
Personal open-source project
Automated tests
69 (pytest)
Built with
Python · discord.py · FastAPI
Live demo
Runs locally — not hosted
NexusGuard overview: security events, raid status and recent actions for a server

Overview

What it does

NexusGuard watches member joins, messages, role changes and manual moderation in a Discord server. Each event goes through a detection engine, a rule engine and an action engine; the bot then acts in Discord and records the full decision for a live web dashboard. Slash commands are deliberately few — servers are configured and incidents reviewed in the dashboard.

Problem

Moderation bots tend to be either a pile of /ban commands or an opaque auto-mod that acts without saying why. A server team needs automatic reactions to floods and raids, an answer to “why did it do that?”, and a guarantee that one bad rule can't kick half the server.

Solution

Discord events are normalised into plain events and run through six detectors with sliding windows and cooldowns. The rule engine escalates repeat offenders and writes every adjustment down as a note. The action engine runs behind per-server safety caps, and each action is stored as done, skipped, suppressed or failed — with Discord's reason.

Screenshots

From the running application

  • NexusGuard overview: security events, raid status and recent actions for a server
    NexusGuard overview: security events, raid status and recent actions for a server
  • Event detail showing what the detection, rule and action engines decided
    Event detail showing what the detection, rule and action engines decided
  • Raid mode switched on automatically by a join spike
    Raid mode switched on automatically by a join spike
  • Per-server protection rules with bounded thresholds and actions
    Per-server protection rules with bounded thresholds and actions
  • Moderation log with action statuses
    Moderation log with action statuses
  • Overview on a phone
    Overview on a phone

Captured from the app running locally with its seeded demo data. Select an image to open it full size.

Architecture

How the pieces fit

Discord gatewayjoins · messages · rolesDemo simulatorscenarios.pyBot adapterdiscord.py → eventsDetection engine6 detectors · windowsRule engineescalation · notesSafety limitscaps per minuteAction engineone module per actionDiscord RESTDatabaseDashboard
  • Discord gateway → Bot adapter
  • Bot adapter → Detection engine
  • Demo simulator → Detection engine
  • Detection engine → Rule engine
  • Rule engine → Safety limits
  • Safety limits → Action engine
  • Action engine → Discord REST
  • Action engine → Database
  • Database → Dashboard
  • Core service
  • External system
  • Storage / entry point

Core features

What's in it

  • Six detectors

    Message flood, duplicate messages, mass mentions, join-spike raids, account age and privileged-role grants — each threshold configurable within fixed bounds.

  • Automatic raid mode

    A join spike switches raid mode on for a configured time; a raid of 20 accounts produces one alert, not 20.

  • Explainable decisions

    Every event keeps the detection, the rule engine's notes and each action's result, so the dashboard can always show why.

  • Safety limits

    Per-server caps per minute on kicks, timeouts, role changes, warnings and alerts. Bans are not an action at all.

  • OAuth2 dashboard

    Discord sign-in with guild-permission checks, rules, moderation log, raid status and a permissions/intents health check.

  • Labelled demo data

    Simulated servers driven by a scenario simulator; simulated data is labelled everywhere and never mixes with real servers.

Technical decisions

Interesting problems, and how they're solved

  1. 01

    The engine knows nothing about Discord

    It talks to the world through two protocols, ActionExecutor and Store. The live bot, the demo simulator and the tests run the same detection, rule and action code — only the edges differ.

  2. 02

    Cooldowns turn a burst into one event

    When a detector fires, the member's window is cleared and a cooldown starts, so moderators get one incident per burst instead of a wall of alerts.

  3. 03

    Severity from the shape of the burst

    Detectors fire the moment a threshold is reached, so severity is rule-based and documented per detector — no scores, no model.

  4. 04

    Failure degrades to “log and tell a human”

    Anything past a safety cap is recorded as suppressed; a misconfigured rule can't escalate into mass kicks.

backend/app/engine/detectors/spam.py · L37–63View on GitHub ↗
class MessageFloodDetector:
    module = Module.MESSAGE_FLOOD
    handles = (MessageEvent,)

    def detect(self, event: MessageEvent, rule: MessageFloodRule, state: WindowStore) -> Detection | None:
        key = f"flood:{event.guild_id}:{event.author.id}"
        if state.cooling_down(key, event.at):
            return None
        hits = state.add(key, event.at, rule.window_seconds)
        count = len(hits)
        if count < rule.max_messages:
            return None
        state.clear(key)
        state.start_cooldown(key, event.at, rule.window_seconds)
        span = max((hits[-1][0] - hits[0][0]).total_seconds(), 0.1)
        severity = Severity.HIGH if span <= rule.window_seconds / 2 else Severity.MEDIUM
        return Detection(
            module=self.module,
            type=EventType.SPAM_DETECTED,
            severity=severity,
            summary=f"{count} messages in {span:.1f} s",
            user=event.author,
            channel_id=event.channel_id,
            message_id=event.message_id,
            metadata={"messages": count, "window_seconds": rule.window_seconds, "threshold": rule.max_messages,
                      "kind": "flood"},
        )

Stack & skills

Built with

Technology

  • Python
  • discord.py
  • FastAPI
  • SQLAlchemy
  • Discord OAuth2
  • Next.js
  • TypeScript
  • shadcn/ui

What this project demonstrates

  • Discord bot architecture
  • Event-driven pipelines
  • Ports-and-adapters design
  • Safety engineering
  • OAuth2 dashboards

Repository & demo

Run it yourself

Runs locally with simulated servers and a live event simulator; no Discord token needed for the demo.

No public live demo is hosted. Everything below runs locally with Python 3.12+ and Node 20+.

$ git clone https://github.com/Moeijiro/nexusguard.git
$ cd nexusguard
$ make install
$ make seed
$ make api   # FastAPI on :8000
$ make web   # dashboard on :3000

Have a project in mind?

I build APIs, automation systems, Discord integrations and full-stack applications. Tell me what it should do and I'll tell you how I'd build it.