Solifex Studios
Browse the wiki

Reference

Developer API

Integrate your own plugin: check account links, listen for events, and send messages to Discord.

DiscordSynthesis exposes an Event API for reacting to chat, account linking, and config reloads, and an Action API for sending messages and looking up players. The API classes live in me.osayed.discordsynthesis.core.api and ship inside the normal plugin JAR.

This is a Java API for plugins running on Spigot/Paper, BungeeCord, or Velocity. It does not provide an HTTP endpoint for an external website or service; that needs an integration plugin of your own.

Setup

Which JAR do I need?

Use the same DiscordSynthesis JAR your server runs as a compile-only dependency. There is no separate API JAR or published Maven artifact. The me.osayed.discordsynthesis package is not relocated. Do not shade or bundle DiscordSynthesis into your plugin.

The examples below use version 13.4.9. Replace the version and filename with the release installed on your server.

bashMaven — install the downloaded JAR locally
mvn install:install-file -Dfile=DiscordSynthesis-13.4.9.jar -DgroupId=me.osayed -DartifactId=DiscordSynthesis -Dversion=13.4.9 -Dpackaging=jar
xmlpom.xml
<dependency>
    <groupId>me.osayed</groupId>
    <artifactId>DiscordSynthesis</artifactId>
    <version>13.4.9</version>
    <scope>provided</scope>
</dependency>
groovyGradle alternative
dependencies {
    compileOnly files('libs/DiscordSynthesis-13.4.9.jar')
}

JDA dependencies

JDA is not bundled in the DiscordSynthesis JAR. On Spigot/Paper, DiscordSynthesis loads it through libraries: in plugin.yml. If you use JDA types such as EmbedBuilder, Message, Member, or TextChannel, compile against the same JDA version as your DiscordSynthesis release (6.1.2 for these examples) and make it available to your plugin at runtime too.

yamlplugin.yml — when your code uses JDA types
libraries:
  - net.dv8tion:JDA:6.1.2

The event system, IDSPlayer, and ChannelType need no extra dependency unless your code references JDA types.

Load order

DiscordSynthesis must load before your plugin calls it. Use a hard dependency if your integration requires it, or a soft dependency if your plugin can work without it.

yamlSpigot/Paper plugin.yml or BungeeCord bungee.yml
depend: [DiscordSynthesis]
# For an optional integration, use instead:
# softdepend: [DiscordSynthesis]
javaVelocity — optional dependency
@Plugin(id = "myplugin", dependencies =
    @Dependency(id = "discordsynthesis", optional = true))

Getting the plugin instance

All three platform entry classes implement me.osayed.discordsynthesis.core.IDiscordSynthesis. Check for a missing plugin if you declared an optional dependency, and call into the core only once DiscordSynthesis has initialized.

javaSpigot / Paper
import me.osayed.discordsynthesis.core.IDiscordSynthesis;
import org.bukkit.Bukkit;
 
IDiscordSynthesis ds = (IDiscordSynthesis) Bukkit
    .getPluginManager().getPlugin("DiscordSynthesis");
javaBungeeCord
IDiscordSynthesis ds = (IDiscordSynthesis) ProxyServer.getInstance()
    .getPluginManager().getPlugin("DiscordSynthesis");
javaVelocity
IDiscordSynthesis ds = (IDiscordSynthesis) proxyServer.getPluginManager()
    .getPlugin("discordsynthesis")
    .flatMap(PluginContainer::getInstance)
    .orElse(null);

Checking account linking

To check whether a Minecraft account is linked to Discord, look up its stored Discord ID using the player's UUID. This reads the plugin's cached database; an empty ID means no link is recorded on this node.

javaSpigot / Paper — after DiscordSynthesis initializes
import me.osayed.discordsynthesis.core.IDiscordSynthesis;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
 
public boolean isLinked(OfflinePlayer player) {
    IDiscordSynthesis ds = (IDiscordSynthesis) Bukkit
        .getPluginManager().getPlugin("DiscordSynthesis");
    if (ds == null || ds.getCore() == null
            || ds.getCore().getDatabase() == null) {
        throw new IllegalStateException("DiscordSynthesis is not ready");
    }
 
    String discordId = ds.getCore().getDatabase()
        .getPlayerDiscordID(player.getUniqueId().toString());
    return discordId != null && !discordId.isEmpty();
}

If your custom system supports PlaceholderAPI, %discordsynthesis_linked% already returns true or false. See Placeholders. To react to changes, subscribe to AccountLinkedEvent and AccountUnlinkedEvent below.

Linked and online are different checks

A linked account may be offline. IDSPlayer.isOnline() checks Minecraft connection status, not Discord presence. On a network, use the shared database configuration and allow for the cache refresh interval when a link changes on another node. See Database.

Event API

Register your listener with ds.getCore().getApiManager().subscribe(listener) and remove it with unsubscribe(listener) when your plugin disables. These are DiscordSynthesis events; register them through its API manager.

javaA complete Spigot / Paper link listener
import me.osayed.discordsynthesis.core.IDiscordSynthesis;
import me.osayed.discordsynthesis.core.api.Subscribe;
import me.osayed.discordsynthesis.core.api.events.AccountLinkedEvent;
import me.osayed.discordsynthesis.core.api.events.AccountUnlinkedEvent;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
 
public class MyPlugin extends JavaPlugin {
    private IDiscordSynthesis ds;
    private final LinkListener listener = new LinkListener();
 
    @Override
    public void onEnable() {
        ds = (IDiscordSynthesis) Bukkit.getPluginManager()
            .getPlugin("DiscordSynthesis");
        if (ds == null) return;
        ds.getCore().getApiManager().subscribe(listener);
    }
 
    @Override
    public void onDisable() {
        if (ds != null && ds.getCore() != null
                && ds.getCore().getApiManager() != null) {
            ds.getCore().getApiManager().unsubscribe(listener);
        }
    }
 
    public class LinkListener {
        @Subscribe
        public void onLink(AccountLinkedEvent event) {
            getLogger().info(event.getMinecraftName()
                + " linked to " + event.getDiscordId());
        }
 
        @Subscribe
        public void onUnlink(AccountUnlinkedEvent event) {
            getLogger().info(event.getMinecraftName() + " unlinked");
        }
    }
}

Handler signatures and registration

  • Use a public method returning void, annotated with me.osayed.discordsynthesis.core.api.Subscribe, with exactly one concrete event parameter.
  • Declare handler methods directly on the listener class. Inherited handlers are not registered.
  • Dispatch matches the exact event class. Handlers for base types Event, GameEvent, and DiscordEvent never fire.
  • subscribe() throws IllegalArgumentException for a null or duplicate listener, no annotated handlers, or invalid event parameters.
  • ApiManager also exposes unsubscribeAll(), isRegistered(Object), getRegisteredListenerCount(), and getRegisteredMethodCount(). Use unsubscribe(listener) to remove only your own listener.

Priorities and cancellation

Set @Subscribe(priority = ListenerPriority.HIGH) using me.osayed.discordsynthesis.core.api.ListenerPriority. Handlers run in this order: LOWEST, LOW, NORMAL (default), HIGH, HIGHEST, MONITOR. Use MONITOR to observe the result without changing it.

Cancel or modify events at NORMAL priority or earlier. Cancelled events still reach later handlers, so check isCancelled() if you do not want to process an event another plugin cancelled.

javaModify or cancel Minecraft-to-Discord chat
import me.osayed.discordsynthesis.core.api.Subscribe;
import me.osayed.discordsynthesis.core.api.events.GameChatMessagePreProcessEvent;
 
@Subscribe
public void onChat(GameChatMessagePreProcessEvent event) {
    if (event.isCancelled()) return;
    if (event.getMessage().contains("spam")) {
        event.setCancelled(true);
        return;
    }
    event.setMessage(event.getMessage().replace("oldword", "newword"));
}

Available events

Event types live in me.osayed.discordsynthesis.core.api.events. Every event exposes getTimestamp() (milliseconds) and getEventName(). Exceptions thrown by handlers are caught and logged without stopping the remaining listeners.

EventCancellableWhen it fires
GameChatMessagePreProcessEventyesBefore Minecraft chat is filtered and sent to Discord.
GameChatMessagePostProcessEventnoAfter a Minecraft chat message is sent to Discord.
DiscordMessageReceivedEventyesBefore a received Discord message is relayed to Minecraft; requires Chat Sync.
AccountLinkedEventnoWhen an account is linked via /connect, /ds link, or Discord verification.
AccountUnlinkedEventnoWhen /disconnect unlinks an account, including staff unlinking.
PluginReloadedEventnoAfter DiscordSynthesis reloads its configs.

GameChatMessagePreProcessEvent

MethodReturns / effect
getPlayer()The sender as IDSPlayer.
getMessage() / setMessage(String)Read or replace the raw message before the chat filter runs.
getChannel()The target Discord channel ID.
isCancelled() / setCancelled(boolean)Stop delivery to Discord. The Minecraft message still appears in-game.

Note

Although setChannel(...) exists, the relay resolves its target before dispatching this event. Treat the channel as read-only for this integration.

GameChatMessagePostProcessEvent

MethodReturns
getPlayer()The sender as IDSPlayer.
getMessage()The final message after filtering and formatting.
getChannel()The Discord channel ID.
wasWebhook()Whether delivery used a webhook rather than the bot.

DiscordMessageReceivedEvent

MethodReturns / effect
getMessage()The original JDA Message.
getMember()The JDA Member who sent it.
getChannel()The JDA TextChannel.
getProcessedMessage() / setProcessedMessage(String)Read or replace the relay text; initially the message's display content.
isCancelled() / setCancelled(boolean)Stop the message from reaching Minecraft.

AccountLinkedEvent and AccountUnlinkedEvent

MethodReturnsEvents
getMinecraftUUID()Minecraft UUID.Both
getMinecraftName()Minecraft username.Both
getDiscordId()Linked or unlinked Discord user ID.Both
getDiscordName()Discord username.Linked only

If a stored Minecraft UUID is malformed, unlinking still goes through but AccountUnlinkedEvent is skipped.

PluginReloadedEvent

This event has no additional methods and fires after configs reload. DiscordSynthesis can restart its core during a reload, and shutting down the API manager clears listeners. If your integration survives a reload or disable/re-enable, obtain the current manager and register again once it is ready; a listener cleared during shutdown cannot receive the subsequent reload event.

Threading

Events fire on the thread that performed the original action. DiscordMessageReceivedEvent, and linking events triggered by Discord verification, arrive on JDA callback threads. Schedule back onto the platform's required thread before using thread-sensitive game APIs.

javaSpigot / Paper — inside a handler in your JavaPlugin
Bukkit.getScheduler().runTask(this, () -> {
    // Use main-thread-only Bukkit APIs here.
});

Action API

DiscordSynthesisAPI is a singleton initialized during startup. get() throws a checked Exception if the implementation is unavailable. Obtain it when you need it, after DiscordSynthesis has started, rather than caching it during your plugin's startup.

javaInside a method that handles or declares Exception
import me.osayed.discordsynthesis.core.api.DiscordSynthesisAPI;
import me.osayed.discordsynthesis.core.api.ChannelType;
 
DiscordSynthesisAPI api = DiscordSynthesisAPI.get();
api.sendMessage(ChannelType.CHATSYNC, "Hello from my plugin");

Sending messages and embeds

javaEmbed messages — requires JDA
import java.awt.Color;
import net.dv8tion.jda.api.EmbedBuilder;
 
EmbedBuilder embed = new EmbedBuilder()
    .setTitle("Event started")
    .setColor(Color.GREEN);
api.sendMessage(ChannelType.BROADCAST, embed);

Both sendMessage overloads send asynchronously. The destination comes from the server's channel configuration. A call does nothing if the target channel is not configured.

ChannelTypeConfigured channel
CHATSYNCChat sync
COMMANDLOGCommand log
SERVERSTATUSServer status
CONSOLEConsole
LEAVEJOINPlayer join / leave
DEATHLOGDeath log
REPORTSReports
BROADCASTBroadcasts

Sending as a player with webhooks

Send under a player's name and skin, as chat sync does. The target channel must already have a webhook. sendChatSyncWebhook logs a warning and returns when none exists.

javadsPlayer is an IDSPlayer
api.sendChatSyncWebhook(dsPlayer, "message text", "DisplayName");
api.sendCommandLogWebhook(dsPlayer, "/warp spawn", "DisplayName");

Looking up Minecraft players

java
import me.osayed.discordsynthesis.core.IDSPlayer;
 
IDSPlayer player = api.getPlayer("Notch");
boolean online = player != null && player.isOnline();

IDSPlayer is the platform-independent wrapper. It exposes getName(), getUniqueId(), getServerName(), getPlayerIP(), isOnline(), isPlayer(), setPlaceholders(String), connect(String), and playMentionSound(). Use a fresh lookup when checking connection status.

Compatibility

  • The API is versioned with the plugin. Compile against the release your server runs.
  • Keep your listeners registered once per instance and unsubscribe them when your plugin disables.
  • Use the database link check for account linking and the player wrapper for Minecraft online status. Discord presence is a separate integration.
  • On proxy networks, choose the node whose player and linking data your integration needs. See Proxy & networks.