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.
mvn install:install-file -Dfile=DiscordSynthesis-13.4.9.jar -DgroupId=me.osayed -DartifactId=DiscordSynthesis -Dversion=13.4.9 -Dpackaging=jar<dependency>
<groupId>me.osayed</groupId>
<artifactId>DiscordSynthesis</artifactId>
<version>13.4.9</version>
<scope>provided</scope>
</dependency>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.
libraries:
- net.dv8tion:JDA:6.1.2The 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.
depend: [DiscordSynthesis]
# For an optional integration, use instead:
# softdepend: [DiscordSynthesis]@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.
import me.osayed.discordsynthesis.core.IDiscordSynthesis;
import org.bukkit.Bukkit;
IDiscordSynthesis ds = (IDiscordSynthesis) Bukkit
.getPluginManager().getPlugin("DiscordSynthesis");IDiscordSynthesis ds = (IDiscordSynthesis) ProxyServer.getInstance()
.getPluginManager().getPlugin("DiscordSynthesis");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.
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.
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 withme.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, andDiscordEventnever fire. subscribe()throwsIllegalArgumentExceptionfor a null or duplicate listener, no annotated handlers, or invalid event parameters.ApiManageralso exposesunsubscribeAll(),isRegistered(Object),getRegisteredListenerCount(), andgetRegisteredMethodCount(). Useunsubscribe(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.
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.
| Event | Cancellable | When it fires |
|---|---|---|
GameChatMessagePreProcessEvent | yes | Before Minecraft chat is filtered and sent to Discord. |
GameChatMessagePostProcessEvent | no | After a Minecraft chat message is sent to Discord. |
DiscordMessageReceivedEvent | yes | Before a received Discord message is relayed to Minecraft; requires Chat Sync. |
AccountLinkedEvent | no | When an account is linked via /connect, /ds link, or Discord verification. |
AccountUnlinkedEvent | no | When /disconnect unlinks an account, including staff unlinking. |
PluginReloadedEvent | no | After DiscordSynthesis reloads its configs. |
GameChatMessagePreProcessEvent
| Method | Returns / 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
| Method | Returns |
|---|---|
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
| Method | Returns / 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
| Method | Returns | Events |
|---|---|---|
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.
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.
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
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.
| ChannelType | Configured channel |
|---|---|
CHATSYNC | Chat sync |
COMMANDLOG | Command log |
SERVERSTATUS | Server status |
CONSOLE | Console |
LEAVEJOIN | Player join / leave |
DEATHLOG | Death log |
REPORTS | Reports |
BROADCAST | Broadcasts |
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.
api.sendChatSyncWebhook(dsPlayer, "message text", "DisplayName");
api.sendCommandLogWebhook(dsPlayer, "/warp spawn", "DisplayName");Looking up Minecraft players
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.