Warring NationsWarring Nations MC 1.21.1.NEOFORGE.JAVA 21
Wiki/Developer/Discord API Reference

Discord API Reference

A stable, JDA-free surface that other Warring Nations addons build on to reach WN Discord, the same surface WN Discord's own built-in commands use internally.

ADDON DEVS

Entry Point

Everything lives under one Java package, and every outbound method on it is non-blocking.

PACKAGE

dev.tacyeet.wndiscord.api

DiscordApi.get() returns an Optional-style bridge handle, empty when WN Discord isn't installed or hasn't finished starting. Every actual call happens inside the ifPresent callback:

DiscordApi.get().ifPresent(bridge -> {
    bridge.registerChannel("myaddon", "My addon's feed");
});
i
Non-blocking guarantee. Every outbound method on the bridge is non-blocking: registerChannel, sendEmbed, and registerSlashCommand return without waiting on a Discord round trip, so calling this API never stalls the server thread on network I/O.

Registering a Channel

bridge.registerChannel("myaddon", "My addon's feed") declares a named feed channel for an addon, the same mechanism WN Siege uses to register its /siege command's own channel presence and the same one behind WN Discord's own built-in nation/war/diplomacy/village/government feeds.

Example use case

WN BlueMap posts a map link to WN Discord whenever a war starts or ends. Registering its own channel first gives it a stable destination to send that embed to.

Sending an Embed

bridge.sendEmbed(ChannelRef.of("myaddon"), DiscordEmbed.builder()...build()) posts a rich embed to a registered channel. DiscordEmbed.builder() takes at least color(int), title(String), and description(String).

Registering a Slash Command

bridge.registerSlashCommand(SlashCommand.builder("mycmd", "Does a thing").handler(interaction -> interaction.reply("Done!")).build()) adds a new top-level slash command through the same registration path WN Discord's own 17 top-level commands use.

i
Shows up in help automatically. WN Discord's /help command is generated entirely from the live command registry, so it never drifts and covers any command an addon registers too, alongside WN Discord's own built-ins.
Example use case

WN Siege registers a /siege command through this API rather than shipping its own separate Discord bot integration.

Full Example

The three pieces above, combined into one addon-startup block: register a channel, post an embed to it, and add a slash command.

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());
});

Registering a Placeholder

registerPlaceholder adds a new templated token an addon can use in its own embeds and messages, the same mechanism behind WN Discord's own built-in placeholders like %gov_entity%, %gov_tally%, and the bot presence text's %online%/%activewars%/%currentwar% tokens. WN Discord's own ~220 templated strings are built on this same substitution mechanism, so an addon-registered placeholder works anywhere a template string is rendered, not just in content the addon itself sends.

hubOnly() and adminOnly() Defaults

WN Discord distinguishes the shared hub guild from a nation's own linked Discord server (see Per-nation Discord servers), and staff-only commands from player-facing ones. A registered command or channel opts into the same two gates WN Discord's own built-in surface already uses:

hubOnly()

Restricts registration to the shared hub guild, never publishing to, or running from, a nation's own linked Discord server. WN Discord's own /setup, /config, /debug, /staff, and /whitelist namespaces are hub-only for exactly this reason: they read or mutate bridge-wide identity and configuration, or exercise server-wide power a nation's own Discord has no business reaching. Any staff-only command is hub-only by default unless a maintainer deliberately opts it out.

adminOnly()

Hides the command from non-staff automatically, the same gate WN Discord's own five staff-only namespaces (/staff, /whitelist, /setup, /config, /debug) use, discord-side, with no separate permission wiring required from the addon.

What's Already Built On This API

This isn't a theoretical surface: WN Discord's own two companion addons are the existing reference implementations.

AddonUses the API for
WN SiegeRegisters a /siege command through the extensibility API.
WN BlueMapPosts a map link to a registered channel whenever a war starts or ends.

Error Behavior

The bridge is written to be safe to call carelessly: the one check worth doing up front is DiscordApi.get() being empty, and after that every outbound method is documented as never throwing.

Never throws

DiscordBridge's own class doc states it plainly: all outbound methods are non-blocking and never throw. There is no checked or runtime exception to catch around sendEmbed, registerChannel, or registerSlashCommand.

Disconnected bridge

A message is queued onto the dispatch thread and delivered asynchronously, or dropped with a logged warning if the bridge is disconnected. Either way the call site gets no return-value signal; check isReady() first if the caller needs to know before sending.

Registration is idempotent-friendly

registerChannel/registerSlashCommand are documented as safe to call during server start, so an addon doesn't need to guard against calling registration more than once.

Threading

Every outbound method on DiscordBridge is non-blocking by design, so calling it never stalls the caller on a Discord round trip.

i
Queued, not synchronous. A call like sendEmbed queues the message onto the bridge's own dispatch thread and returns immediately; the actual Discord REST call happens asynchronously off that queue, not on the calling thread.