Skip to content

Conversation

@Jakubk15
Copy link
Member

This pull request introduces Discord integration to the ParcelLockers plugin, enabling users to link their Minecraft accounts with Discord accounts and receive notifications. The changes include configuration options, message customization, and new commands for linking and unlinking accounts. The implementation adds several new classes and updates the plugin's lifecycle to support Discord connectivity and clean shutdown.

Discord Integration Features

  • Added Discord integration settings to PluginConfig, allowing configuration of bot token, server/channel IDs, and admin role ID. [1] [2]
  • Implemented DiscordClientManager to manage Discord client lifecycle (login and logout) and expose the connected client for other components.
  • Created DiscordLink, DiscordNotificationType, and repositories to support linking Minecraft and Discord accounts. [1] [2]

Command and Repository Enhancements

  • Registered new commands: DiscordLinkCommand and DiscordUnlinkCommand for account management, with supporting repository for persistent links. [1] [2]

Configuration and Messaging

  • Extended MessageConfig to include customizable Discord-related messages for linking, unlinking, and verification flows. [1] [2]

Plugin Lifecycle Updates

  • Updated plugin startup (onEnable) to initialize Discord integration if enabled and validate configuration, disabling the plugin if required properties are missing.
  • Ensured clean shutdown of Discord client during plugin disable (onDisable).

Minor Refactoring

  • Minor refactor of metrics and updater service initialization for consistency.

@Jakubk15 Jakubk15 added the 🆕 feature New feature or request label Jan 17, 2026
@Jakubk15 Jakubk15 marked this pull request as draft January 17, 2026 14:54
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant new feature: Discord integration. The implementation is comprehensive, covering configuration, commands for linking/unlinking accounts, and the necessary database persistence.

My review focuses on several key areas:

  • Critical Blocking Calls: There are several instances of blocking network and I/O calls (.block()) on the main server thread. These will cause your server to freeze and must be addressed. I've marked these as critical.
  • Code Readability: Some methods with asynchronous logic have deeply nested callbacks, which can be refactored for better readability and maintainability.
  • Configuration Defaults: The default values in the configuration should be set to be more generic for a public plugin.
  • Minor Logic Errors: I found a few minor issues, such as a duplicated condition and incorrect message targets for admin commands.

Overall, this is a great addition. Addressing the blocking calls is the highest priority before this can be safely used on a production server.

Comment on lines +372 to +379
@Comment("# The Discord server ID.")
public String serverId = "1179117429301977251";

@Comment("# The Discord channel ID for parcel notifications.")
public String channelId = "1317827115147853834";

@Comment("# The Discord role ID for bot administrators.")
public String botAdminRoleId = "1317589501169893427";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The default values for serverId, channelId, and botAdminRoleId are hardcoded with specific IDs. For a distributable plugin, it's better to leave these as empty strings by default. This forces server administrators to configure them explicitly, avoiding confusion and potential errors if they don't notice the pre-filled values.

Suggested change
@Comment("# The Discord server ID.")
public String serverId = "1179117429301977251";
@Comment("# The Discord channel ID for parcel notifications.")
public String channelId = "1317827115147853834";
@Comment("# The Discord role ID for bot administrators.")
public String botAdminRoleId = "1317589501169893427";
@Comment("# The Discord server ID.")
public String serverId = "";
@Comment("# The Discord channel ID for parcel notifications.")
public String channelId = "";
@Comment("# The Discord role ID for bot administrators.")
public String botAdminRoleId = "";

Jakubk15 and others added 6 commits January 17, 2026 16:07
…/DiscordLinkRepositoryOrmLite.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…/DiscordLinkRepositoryOrmLite.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@Jakubk15 Jakubk15 linked an issue Jan 17, 2026 that may be closed by this pull request
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Discord integration to the ParcelLockers plugin, enabling players to link their Minecraft accounts with Discord for notifications. The implementation includes account linking/unlinking commands with verification flow, repository layer for persistence, and Discord client lifecycle management.

Changes:

  • Added Discord client manager with async login/logout functionality
  • Implemented account linking commands with verification code flow using Paper's Dialog API
  • Created repository layer with OrmLite for storing Discord-Minecraft account links
  • Extended configuration with Discord settings and related messages

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
build.gradle.kts Added discord4j-core dependency and invalid JUnit version
DiscordLink.java Simple record for linking Minecraft UUID to Discord ID
DiscordNotificationType.java Unused enum defining notification delivery types
DiscordClientManager.java Manages Discord client lifecycle with async initialization issues
DiscordLinkRepository*.java Repository interface, entity, and OrmLite implementation with query bug
DiscordLinkCommand.java Account linking command with verification flow and security concerns
DiscordUnlinkCommand.java Account unlinking command for self and admin operations
PluginConfig.java Discord configuration settings with hardcoded test values
MessageConfig.java Discord-related user messages with misleading error text
ParcelLockers.java Plugin initialization with race condition and null handling issues

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import org.bukkit.entity.Player;
import reactor.core.publisher.Mono;

@SuppressWarnings("UnstableApiUsage")
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dialog API (io.papermc.paper.dialog) is marked as @UnstableApiUsage, which is acknowledged in the @SuppressWarnings annotation. However, this API is experimental and may change or be removed in future Paper versions, potentially breaking the verification flow. Consider documenting this dependency and having a fallback mechanism, or using a more stable approach like chat-based verification.

Suggested change
@SuppressWarnings("UnstableApiUsage")
/**
* Command responsible for linking a Minecraft account with a Discord account.
* <p>
* This implementation relies on Paper's Dialog API ({@code io.papermc.paper.dialog}),
* which is marked as unstable and may change or be removed in future Paper versions.
* If the Dialog API becomes unavailable, this command may need to be updated or
* replaced with a more stable flow (for example, chat-based verification).
*/

Copilot uses AI. Check for mistakes.
.build();
.missingPermission(new MissingPermissionsHandlerImpl(noticeService));

DiscordLinkRepository discordLinkRepository = new DiscordLinkRepositoryOrmLite(databaseManager, scheduler);
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DiscordLinkRepository is initialized on line 199 even when Discord integration is disabled (config.discord.enabled = false). This creates an unnecessary database table and wastes resources. The repository should only be initialized when Discord integration is enabled.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +33
DiscordClient.create(this.token)
.login()
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(client -> {
this.client = client;
this.logger.info("Successfully logged in to Discord.");
})
.doOnError(error -> {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
})
.subscribe();
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When Discord login fails, the error is logged but the plugin continues running with a null client. This will cause NullPointerExceptions when commands try to use the client. Consider throwing an exception or disabling the Discord integration when login fails to prevent the plugin from entering an invalid state.

Suggested change
DiscordClient.create(this.token)
.login()
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(client -> {
this.client = client;
this.logger.info("Successfully logged in to Discord.");
})
.doOnError(error -> {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
})
.subscribe();
try {
GatewayDiscordClient loggedInClient = DiscordClient.create(this.token)
.login()
.block();
if (loggedInClient == null) {
this.logger.severe("Failed to log in to Discord: login returned null client.");
throw new IllegalStateException("Discord login failed: client is null");
}
this.client = loggedInClient;
this.logger.info("Successfully logged in to Discord.");
} catch (Exception error) {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
throw new IllegalStateException("Discord login failed", error);
}

Copilot uses AI. Check for mistakes.
return;
}

if (!verificationData.code().equals(enteredCode)) {
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an invalid verification code is entered, the code is not invalidated from the cache (lines 210-212). This means a user can keep trying to guess the code until they get it right or until the 2-minute expiration occurs, which creates a security vulnerability. The verification should have a limited number of attempts, or the code should be invalidated after a failed attempt.

Suggested change
if (!verificationData.code().equals(enteredCode)) {
if (!verificationData.code().equals(enteredCode)) {
// Invalidate the verification code after a failed attempt to prevent repeated guessing
this.authCodesCache.invalidate(playerUuid);

Copilot uses AI. Check for mistakes.
Comment on lines +218 to +226
liteCommandsBuilder.commands(
new DiscordLinkCommand(
this.discordClientManager.getClient(),
discordLinkRepository,
noticeService,
miniMessage,
messageConfig),
new DiscordUnlinkCommand(discordLinkRepository, noticeService)
);
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Discord client is passed to DiscordLinkCommand via getClient() immediately after initialization, which may return null due to the asynchronous login in initialize(). This will cause a NullPointerException when the command is executed and tries to use the client.

Suggested change
liteCommandsBuilder.commands(
new DiscordLinkCommand(
this.discordClientManager.getClient(),
discordLinkRepository,
noticeService,
miniMessage,
messageConfig),
new DiscordUnlinkCommand(discordLinkRepository, noticeService)
);
var discordClient = this.discordClientManager.getClient();
if (discordClient == null) {
this.getLogger().severe("Failed to initialize Discord client. Discord commands will not be available.");
}
else {
liteCommandsBuilder.commands(
new DiscordLinkCommand(
discordClient,
discordLinkRepository,
noticeService,
miniMessage,
messageConfig),
new DiscordUnlinkCommand(discordLinkRepository, noticeService)
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +371 to +379

@Comment("# The Discord server ID.")
public String serverId = "1179117429301977251";

@Comment("# The Discord channel ID for parcel notifications.")
public String channelId = "1317827115147853834";

@Comment("# The Discord role ID for bot administrators.")
public String botAdminRoleId = "1317589501169893427";
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Discord-related configuration fields (serverId, channelId, botAdminRoleId) are defined but never used in the implementation. These fields suggest incomplete functionality - they appear to be for server notifications or admin role verification, but no code utilizes them. Consider either implementing the intended functionality or removing these unused configuration fields.

Suggested change
@Comment("# The Discord server ID.")
public String serverId = "1179117429301977251";
@Comment("# The Discord channel ID for parcel notifications.")
public String channelId = "1317827115147853834";
@Comment("# The Discord role ID for bot administrators.")
public String botAdminRoleId = "1317589501169893427";

Copilot uses AI. Check for mistakes.
String enteredCode = view.getText("code");
this.handleVerification(player, enteredCode);
}, ClickCallback.Options.builder()
.uses(1)
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification button is configured with only 1 use (line 177), but if the player enters an invalid code, they cannot try again without restarting the entire verification process. This creates a poor user experience as a simple typo forces them to wait for a new code. Consider allowing multiple attempts or reopening the dialog on invalid code entry.

Suggested change
.uses(1)
.uses(5)

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +46
var queryBuilder = dao.queryBuilder()
.where().eq(ID_COLUMN_NAME, discordId);
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The query builder chain on lines 45-46 is missing proper method chaining. The queryBuilder() method should be followed by where(), but the current code structure breaks this chain incorrectly, creating an incomplete query that will fail at runtime.

Suggested change
var queryBuilder = dao.queryBuilder()
.where().eq(ID_COLUMN_NAME, discordId);
var queryBuilder = dao.queryBuilder();
queryBuilder.where().eq(ID_COLUMN_NAME, discordId);

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +33
DiscordClient.create(this.token)
.login()
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(client -> {
this.client = client;
this.logger.info("Successfully logged in to Discord.");
})
.doOnError(error -> {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
})
.subscribe();
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Discord client login is performed asynchronously in the initialize() method, but getClient() can be called immediately after, potentially returning null. This creates a race condition where commands or other components trying to use the client might receive null before the login completes. Consider blocking until the client is initialized or implementing a check mechanism to ensure the client is ready before use.

Suggested change
DiscordClient.create(this.token)
.login()
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(client -> {
this.client = client;
this.logger.info("Successfully logged in to Discord.");
})
.doOnError(error -> {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
})
.subscribe();
try {
GatewayDiscordClient loggedInClient = DiscordClient.create(this.token)
.login()
.block();
if (loggedInClient != null) {
this.client = loggedInClient;
this.logger.info("Successfully logged in to Discord.");
} else {
this.logger.severe("Failed to log in to Discord: login returned null client.");
}
} catch (Exception error) {
this.logger.severe("Failed to log in to Discord: " + error.getMessage());
error.printStackTrace();
}

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +205
if (config.discord.botToken.isBlank() ||
config.discord.serverId.isBlank() ||
config.discord.channelId.isBlank() ||
config.discord.botAdminRoleId.isBlank()
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null check for botToken uses isBlank(), but if the environment variable is not set, botToken will be null, which will cause a NullPointerException. The check should handle null values explicitly before calling isBlank().

Suggested change
if (config.discord.botToken.isBlank() ||
config.discord.serverId.isBlank() ||
config.discord.channelId.isBlank() ||
config.discord.botAdminRoleId.isBlank()
if (config.discord.botToken == null || config.discord.botToken.isBlank() ||
config.discord.serverId == null || config.discord.serverId.isBlank() ||
config.discord.channelId == null || config.discord.channelId.isBlank() ||
config.discord.botAdminRoleId == null || config.discord.botAdminRoleId.isBlank()

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discord webhooks, bot, plugin integration

2 participants