FishMMO Discord Bot
A standalone .NET 8 application that bridges a live FishMMO server with a Discord guild. The bot relays in-game chat to Discord channels and back, lets players link their Discord account to a FishMMO character, exposes administrative slash-style commands (mute, ban, kick, character lookup), and dynamically provisions per-guild / per-party voice or text channels.
It runs as a long-lived service alongside the rest of the FishMMO server stack.
Table of Contents
- Description
- Supported Platforms
- Architecture
- Key Components
- Configuration
- Build & Run
- Deployment Notes
- Flow Diagram
Description
The bot is built on the Discord.Net client library and the standard
Microsoft.Extensions.Hosting generic host. It composes a fixed set of
modules (command handlers) and services (background workers) through DI, reads
its configuration from appsettings.json, and connects to the FishMMO chat
endpoint to poll chat events and post outbound messages.
Bridging is two-way:
- Game → Discord:
ChatPollingServiceperiodically polls the FishMMO chat REST API and forwards new messages to the configured Discord channels viaGameChatBridgeService. - Discord → Game: Discord chat messages (and slash commands) are intercepted
by
CommandHandlingServiceand pushed back to the FishMMO chat API, subject toRateLimiterServiceandBridgeBanService.
Supported Platforms
| Target | Status |
|---|---|
| .NET 8.0 (Linux, Windows, macOS) | Yes |
| Docker / Linux service | Recommended for production |
| Requirement | Version |
|---|---|
| .NET SDK | 8.0+ |
| Discord application + bot token | Required |
| FishMMO server | Required (for chat API) |
Architecture
FishMMO-DiscordBot/
├── Program.cs # Generic host + DI composition + bot startup
├── ChatChannel.cs # Enum / mapping of FishMMO chat channels
├── appsettings.json # Bot configuration (token, mappings, limits)
├── Data/ # Plain DTOs / configuration POCOs
├── Modules/ # Discord slash + text command handlers
│ ├── AdminModule.cs
│ ├── CharacterModule.cs
│ ├── CommandListModule.cs
│ ├── DatabaseModule.cs
│ ├── GeneralModule.cs
│ ├── LinkModule.cs
│ └── ModerationModule.cs
└── Services/ # Long-running hosted services
├── AccountLinkingService.cs
├── BotConfigurationService.cs
├── BridgeBanService.cs
├── ChatPollingService.cs
├── CommandHandlingService.cs
├── DynamicChannelManagerService.cs
├── GameChatBridgeService.cs
└── RateLimiterService.cs
The generic host wires IHostedService implementations for each background
worker; the bot's lifetime is the host's lifetime.
Key Components
Modules (Discord-side commands)
| Module | Responsibility |
|---|---|
AdminModule |
Owner / admin-only commands (reload config, shutdown, diagnostics). |
CharacterModule |
Character lookup by name / Discord-linked account. |
CommandListModule |
!help / /help — self-documenting command list. |
DatabaseModule |
Read-only DB queries gated behind admin permissions. |
GeneralModule |
Ping, status, server uptime. |
LinkModule |
/link workflow — issues short-lived one-time codes that a player redeems in-game to link Discord ↔ FishMMO account. |
ModerationModule |
Mute / unmute / ban / unban for the chat bridge (uses BridgeBanService). |
Services (long-running workers)
| Service | Responsibility |
|---|---|
BotConfigurationService |
Loads appsettings.json, watches for changes, exposes config to other services. |
AccountLinkingService |
Manages pending link codes and persists confirmed Discord ↔ account mappings. |
ChatPollingService |
Polls FishMMO chat API at a configured interval; emits events to GameChatBridgeService. |
GameChatBridgeService |
Forwards game messages → Discord channels and Discord messages → game chat. |
DynamicChannelManagerService |
Creates / archives Discord channels in response to in-game events (party formed, guild created, etc.). |
CommandHandlingService |
Dispatches inbound Discord messages to Modules/ and handles command results. |
BridgeBanService |
Tracks Discord users banned from the bridge; consulted before forwarding. |
RateLimiterService |
Per-user / per-channel sliding-window rate limiter to prevent spam from either side. |
Data
Data/ holds POCOs used for configuration binding (channel mappings, bridge
policy, rate-limit windows) and DTOs for the FishMMO chat REST contract.
Configuration
Place appsettings.json in the project root. It is copied to the output
directory by the build (<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>).
appsettings.json shape
{
"Discord": {
"Token": "YOUR_DISCORD_BOT_TOKEN",
"Prefix": "!",
"GuildId": "0000000000000000000"
},
"FishMMO": {
"ApiUrl": "http://localhost:5000/api/",
"ApiKey": "YOUR_FISHMMO_API_KEY",
"ChatPollIntervalMs": 1500
},
"ChannelMappings": {
"World": "discord-channel-id",
"Trade": "discord-channel-id",
"Admin": "discord-admin-channel-id"
},
"DynamicChannels": {
"Enabled": true,
"CategoryId": "discord-category-id",
"AutoArchiveMinutes": 60
},
"RateLimits": {
"PerUserPerMinute": 10,
"PerChannelPerMinute": 60
},
"Linking": {
"CodeLengthChars": 8,
"CodeTtlSeconds": 300
},
"ChatRelay": {
"GameToDiscordChannels": [ "Say", "World", "Trade", "Region" ]
},
"BridgeMessageMaxLength": 128
}
| Section | Notes |
|---|---|
Discord.Token |
Secret. Store via environment override in production. |
Discord.Prefix |
Legacy text-command prefix (slash commands are preferred). |
FishMMO.ApiUrl / ApiKey |
FishMMO chat / account REST endpoint. |
ChannelMappings |
In-game chat channel → Discord channel ID. |
DynamicChannels |
Configures DynamicChannelManagerService. |
RateLimits |
Sliding-window settings for RateLimiterService. |
Linking |
Controls /link codes (length and TTL). |
ChatRelay.GameToDiscordChannels |
Allowlist of in-game channels the bot may republish to Discord. Omit for the default (Say, World, Trade, Region). See the warning below. |
BridgeMessageMaxLength |
Caps a Discord message bridged into the game. Keep at or below the game's ChatBroadcast.MaxTextLength (128) — clients discard anything longer, so a larger value makes long messages vanish rather than arrive truncated. |
The relay is an allowlist, and private channels are not configurable. The bot used to select every chat row except Discord's own and forward it, which meant
[Tell]whispers were republished to a public Discord channel — full message body, both character names. Relaying is now opt-in per channel.Tell,Guild,Party,DiscordandCommandare on aNeverRelayableset and are refused even if you name them here, with an error logged; making a private channel relayable is a deliberate code change, not a config edit. Inbound Discord messages are sanitised at the bridge and again server-side, and are no longer exempt from in-game tab filtering.
Production: override
Discord.Token,FishMMO.ApiKey, and any SMTP-like secrets through environment variables (e.g.Discord__Token=…) rather than committing them toappsettings.json.
Build & Run
# Restore + build
dotnet build FishMMO-DiscordBot.sln -c Release
# Run from source
dotnet run --project FishMMO-DiscordBot/FishMMO-DiscordBot.csproj
The process is intended to be supervised — restart on exit. The published
output is a self-contained app suitable for systemd, Windows Service, or
Docker.
Deployment Notes
- The bot needs the
MESSAGE CONTENTandGUILD MEMBERSprivileged intents enabled in the Discord developer portal. - Required Discord scopes:
bot,applications.commands. - Required bot permissions: read/send/manage messages in the bridged channels, manage channels under the configured dynamic category, and (for moderation commands) timeout / ban members.
- Run it on the same network as the FishMMO API so polling latency stays low.
Flow Diagram
flowchart LR
subgraph Discord
DUser[Discord users]
DChan[Bridged channels]
end
subgraph Bot["FishMMO-DiscordBot"]
Host[Generic Host]
Poll[ChatPollingService]
Bridge[GameChatBridgeService]
Cmd[CommandHandlingService]
Link[AccountLinkingService]
Dyn[DynamicChannelManagerService]
Rate[RateLimiterService]
Ban[BridgeBanService]
end
subgraph Game["FishMMO Server"]
API[Chat / Account REST API]
Players[In-game players]
end
Players --> API
API -->|poll| Poll
Poll --> Bridge --> DChan
DUser --> DChan --> Cmd
Cmd -->|allow?| Rate
Cmd -->|allow?| Ban
Cmd --> Bridge --> API --> Players
DUser -->|/link code| Link --> API
API -->|in-game redeem| Link
API -->|"events (party, guild)"| Dyn --> DChan
Host --- Poll
Host --- Bridge
Host --- Cmd
Host --- Link
Host --- Dyn
Host --- Rate
Host --- Ban