03
WN-Discord
SERVER-SIDE ONLY · JDA COMPONENTS V2A control center, not a chat bridge. Live navigable dashboards for nations and wars, guided button and modal actions, functional government voting, two-way chat and console relay, OAuth2 linking, role sync, war threads and scheduled events, betting, staff approvals, and either private per-nation channels or fully separate per-nation Discord servers.
Live dashboards
A dashboard posts as a live tabbed view, not a one-shot reply: anyone can switch tabs, refresh or act, and the message keeps working for whoever clicks it next. Every guided action is gated on the same real, delegable in-game permission the equivalent command checks.
Declare War . Propose Alliance . Create or Invite to a Bloc . Appoint Role . Appoint Governor . staff-only Adjust Treasury.
Live score, siege wall-ring progress, per-objective capture, rollcall readiness, battle log, bet pot and odds, plus Propose Peace and a negotiated-terms modal.
Hub-only. Queues every pending war declaration and nation-creation request with Approve / Deny buttons; denial opens a modal for a reason that reaches the founder.
A war declared here opens its own public thread and a scheduled event timed to the real mobilization deadline; a second event covers peace negotiations.
Jump into a namespace
Discord's own gate does the first layer: a namespace is wholly public or wholly staff-only and hidden from non-staff in the picker. A / reply is always ephemeral; a ! reply is always public, with coordinates and IPs redacted. Help output is generated live from the registry that is actually registered, so it can never drift.
Pending nation creations, banner changes and war declarations post with Approve / Deny buttons; once resolved the buttons are removed and the message records the outcome. With requireLinkedAccountForNationCreation on, an unlinked player gets a clear rejection instead of a silent failure.
Nation lifecycle, war, diplomacy, village and government events, color-coded by severity. A war-start embed is edited through Declared -> Active -> Won -> Ended, rate-limited to once per 30 seconds per war so a capture flurry can't hit Discord's edit limit. Each side gets a rollcall with live I'm In / Can't Make It buttons.
Entities, memberships and proposals are opaque strings -- WN-Discord never branches on them, so every embed works whatever a nation calls its offices. Ballots run Yes / No / Abstain with a live tally, and every notification is per-entity routable to its own channel and role. Full framework on the wiki →
Instead of private channels, a nation links its own separate server: the full command surface minus hub-only /debug, /config and /setup, a chat bridge scoped to its members, synced Linked / Operator / Citizen roles, flag-as-sticker sync, and its own deep feed. No shared 500-channel ceiling.
Chat relays both ways, impersonating the player's name and head; console output mirrors to a hub-only channel in grouped code blocks. An ordered pipeline runs per-user rate limiting, a curated slur blocklist, invite-link blocking and @everyone neutralization -- with per-check totals in /debug queues.
Two deliberately raw hub-only channels, one timestamped line per event. op-audit-log captures every admin command generically off NeoForge's command events, so future subcommands are logged with no wiring.
/discord link runs standard OAuth2 with the guilds scope, so an account outside your guild fails rather than attaching to the wrong server; the one-click link expires in 10 minutes. Linking or unlinking applies or strips roles at once instead of waiting for the next sync pass.
Nearly every piece of Discord-facing text is a config template -- relay lines, ~200 embed keys, role and channel naming, join/leave/death messages, the bot's rotating presence -- rendered defensively so a malformed template falls back to the compiled-in default and logs a warning instead of breaking the relay.
Extensibility API
Everything under dev.tacyeet.wndiscord.api is a stable, JDA-free surface other addons build on -- the built-in commands are registered through this same API. Outbound methods are non-blocking and never throw; a command that is adminOnly() is hubOnly() by default.
DiscordApi.get().ifPresent(bridge -> {
bridge.registerChannel("myaddon", "My addon's feed");
bridge.sendEmbed(ChannelRef.of("myaddon"), DiscordEmbed.builder()
.color(0x5865F2).title("Hello").description("From my addon").build());
bridge.registerSlashCommand(SlashCommand.builder("mycmd", "Does a thing")
.handler(interaction -> interaction.reply("Done!"))
.build());
bridge.registerPlaceholder("myvalue", ctx -> "42");
boolean linked = bridge.links().isLinked(playerUuid);
});
Setup
Dedicated server only. Warring Nations 4.0.0+ is a hard dependency. Drop the jar in mods/, boot once to generate the config, fill it in, restart -- an old flat config is migrated automatically.
Create the application, reset the token, enable the intents you need, then invite from the Installation page with the bot and applications.commands scopes.
View Channels, Send Messages, Embed Links, Read Message History cover the core. Add Attach Files, Manage Webhooks, Manage Roles, Manage Channels, Create Events and Manage Expressions only for the features that need them.
MESSAGE_CONTENT is always required; GUILD_MEMBERS only with role sync on. Environment variables for the token and client secret take priority over the TOML keys.
JDA and its dependencies (OkHttp, Jackson, Kotlin stdlib, Trove) are shaded and relocated under the mod's own package, so they can never collide with another mod's copy.
Every config key on the wiki → . Every command on the wiki →
