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.
- Type
- Personal open-source project
- Automated tests
- 69 (pytest)
- Built with
- Python · discord.py · FastAPI
- Live demo
- Runs locally — not hosted

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 
Event detail showing what the detection, rule and action engines decided 
Raid mode switched on automatically by a join spike 
Per-server protection rules with bounded thresholds and actions 
Moderation log with action statuses
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 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
- 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.
- 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.
- 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.
- 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.
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.
