mirror of
https://github.com/System-End/Discord-MC-Chat.git
synced 2026-04-19 20:55:13 +00:00
Fix ClickEvent API, redesign link messages with inline click-to-copy/suggest, fix help visibility for MC players, fix OP sync on reload
Co-authored-by: Xujiayao <58985541+Xujiayao@users.noreply.github.com>
This commit is contained in:
parent
1945cf2e19
commit
9fbf1773ec
5 changed files with 135 additions and 29 deletions
|
|
@ -4,6 +4,7 @@ import com.xujiayao.discord_mc_chat.client.ClientDMCC;
|
|||
import com.xujiayao.discord_mc_chat.commands.CommandManager;
|
||||
import com.xujiayao.discord_mc_chat.network.NetworkManager;
|
||||
import com.xujiayao.discord_mc_chat.server.ServerDMCC;
|
||||
import com.xujiayao.discord_mc_chat.server.linking.OpSyncManager;
|
||||
import com.xujiayao.discord_mc_chat.utils.CryptUtils;
|
||||
import com.xujiayao.discord_mc_chat.utils.config.ConfigManager;
|
||||
import com.xujiayao.discord_mc_chat.utils.config.ModeManager;
|
||||
|
|
@ -147,6 +148,13 @@ public class DMCC {
|
|||
}
|
||||
|
||||
LOGGER.info(I18nManager.getDmccTranslation("main.init.success"));
|
||||
|
||||
// Trigger OP sync after successful initialization
|
||||
// In single_server mode on first start, this is redundant with the ServerStarted event sync,
|
||||
// but on reload it is necessary since ServerStarted won't fire again.
|
||||
// In standalone mode, this syncs all currently connected clients.
|
||||
OpSyncManager.syncAll();
|
||||
|
||||
return true;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -131,8 +131,15 @@ commands:
|
|||
player_only: "This command can only be used by a Minecraft player."
|
||||
discord_only: "This command can only be used from Discord."
|
||||
not_available: "This command is not available in the current mode."
|
||||
already_linked: "Your Minecraft account is already linked to Discord user \"{}\"."
|
||||
code_generated: "Your verification code is: {0}. Use /link {0} on Discord to complete linking. The code expires in 5 minutes."
|
||||
already_linked: "Your Minecraft account is already linked to Discord user \"{}\". Run "
|
||||
already_linked_unlink: "/dmcc unlink"
|
||||
already_linked_suffix: " to unlink."
|
||||
code_generated_prefix: "Your verification code is: "
|
||||
code_generated_suffix: ". Use "
|
||||
code_generated_discord_cmd: "/link code: {}"
|
||||
code_generated_tail: " on Discord to complete linking. The code expires in 5 minutes. If expired, run "
|
||||
code_generated_refresh_cmd: "/dmcc link"
|
||||
code_generated_refresh_tail: " in-game to get a new code."
|
||||
invalid_code: "Invalid or expired verification code."
|
||||
success: "Successfully linked to Minecraft player \"{}\"!"
|
||||
uuid_already_linked: "This Minecraft account is already linked to another Discord user."
|
||||
|
|
@ -271,9 +278,13 @@ linking:
|
|||
refreshed: "Refreshed verification code {} for player {}!"
|
||||
consumed: "Verification code consumed by player {}: {}!"
|
||||
player_join:
|
||||
not_linked: "Your Minecraft account is not linked to Discord. Use /dmcc link to get a verification code."
|
||||
code_hint: "Use /link {} on Discord within 5 minutes to link your account."
|
||||
click_to_copy: "Click to copy verification code"
|
||||
not_linked_prefix: "Your Minecraft account is not linked to Discord. Use "
|
||||
not_linked_discord_cmd: "/link code: {}"
|
||||
not_linked_middle: " on Discord to complete linking. The code expires in 5 minutes. If expired, run "
|
||||
not_linked_refresh_cmd: "/dmcc link"
|
||||
not_linked_refresh_tail: " in-game to get a new code."
|
||||
click_to_copy: "Click to copy"
|
||||
click_to_run: "Click to fill command"
|
||||
|
||||
minecraft:
|
||||
translations:
|
||||
|
|
|
|||
|
|
@ -131,8 +131,15 @@ commands:
|
|||
player_only: "此命令只能由 Minecraft 玩家使用。"
|
||||
discord_only: "此命令只能在 Discord 上使用。"
|
||||
not_available: "此命令在当前模式下不可用。"
|
||||
already_linked: "你的 Minecraft 账户已绑定到 Discord 用户 \"{}\"。"
|
||||
code_generated: "你的验证码是:{0}。在 Discord 上使用 /link {0} 来完成绑定。验证码将在 5 分钟后过期。"
|
||||
already_linked: "你的 Minecraft 账户已绑定到 Discord 用户 \"{}\"。执行 "
|
||||
already_linked_unlink: "/dmcc unlink"
|
||||
already_linked_suffix: " 解除绑定。"
|
||||
code_generated_prefix: "你的验证码是:"
|
||||
code_generated_suffix: "。在 Discord 上使用 "
|
||||
code_generated_discord_cmd: "/link code: {}"
|
||||
code_generated_tail: " 命令来完成绑定。验证码将在 5 分钟后过期。若过期可在游戏内执行 "
|
||||
code_generated_refresh_cmd: "/dmcc link"
|
||||
code_generated_refresh_tail: " 获取新验证码。"
|
||||
invalid_code: "无效或已过期的验证码。"
|
||||
success: "成功绑定 Minecraft 玩家 \"{}\"!"
|
||||
uuid_already_linked: "此 Minecraft 账户已绑定到另一个 Discord 用户。"
|
||||
|
|
@ -271,9 +278,13 @@ linking:
|
|||
refreshed: "已为玩家 {} 刷新验证码 {}!"
|
||||
consumed: "验证码已被玩家 {} 使用:{}!"
|
||||
player_join:
|
||||
not_linked: "你的 Minecraft 账户尚未绑定 Discord。使用 /dmcc link 获取验证码。"
|
||||
code_hint: "在 5 分钟内在 Discord 上使用 /link {} 来绑定你的账户。"
|
||||
click_to_copy: "点击复制验证码"
|
||||
not_linked_prefix: "你的 Minecraft 账户尚未与 Discord 用户绑定。在 Discord 上使用 "
|
||||
not_linked_discord_cmd: "/link code: {}"
|
||||
not_linked_middle: " 命令来完成绑定。验证码将在 5 分钟后过期。若过期可在游戏内执行 "
|
||||
not_linked_refresh_cmd: "/dmcc link"
|
||||
not_linked_refresh_tail: " 获取新验证码。"
|
||||
click_to_copy: "点击复制"
|
||||
click_to_run: "点击填入命令"
|
||||
|
||||
minecraft:
|
||||
translations:
|
||||
|
|
|
|||
|
|
@ -36,13 +36,13 @@ public class MinecraftCommands {
|
|||
var root = literal("dmcc")
|
||||
.requires(source -> source.hasPermission(ConfigManager.getInt("command_permission_levels.help", -1)))
|
||||
.executes(ctx -> {
|
||||
CommandManager.execute(new MinecraftCommandSender(ctx.getSource()), "help");
|
||||
CommandManager.execute(createSenderForSource(ctx.getSource()), "help");
|
||||
return 1;
|
||||
});
|
||||
var help = literal("help")
|
||||
.requires(source -> source.hasPermission(ConfigManager.getInt("command_permission_levels.help", -1)))
|
||||
.executes(ctx -> {
|
||||
CommandManager.execute(new MinecraftCommandSender(ctx.getSource()), "help");
|
||||
CommandManager.execute(createSenderForSource(ctx.getSource()), "help");
|
||||
return 1;
|
||||
});
|
||||
var info = literal("info")
|
||||
|
|
@ -103,6 +103,22 @@ public class MinecraftCommands {
|
|||
.then(unlink));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the appropriate command sender based on whether the source is a player or console.
|
||||
* <p>
|
||||
* Players get a {@link MinecraftPlayerCommandSender} which provides player context
|
||||
* for commands like link/unlink. Console/command blocks get a plain {@link MinecraftCommandSender}.
|
||||
*
|
||||
* @param source The command source stack.
|
||||
* @return The appropriate command sender.
|
||||
*/
|
||||
private static LocalCommandSender createSenderForSource(CommandSourceStack source) {
|
||||
if (source.getEntity() instanceof ServerPlayer) {
|
||||
return new MinecraftPlayerCommandSender(source);
|
||||
}
|
||||
return new MinecraftCommandSender(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command sender implementation for Minecraft command sources.
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -162,13 +162,9 @@ public class MinecraftEventHandler {
|
|||
// Direct access to server-side managers
|
||||
if (!LinkedAccountManager.isMinecraftUuidLinked(playerUuid)) {
|
||||
String code = VerificationCodeManager.generateOrRefreshCode(playerUuid, playerName);
|
||||
// Notify the player in-game
|
||||
// Notify the player in-game with inline clickable elements
|
||||
ServerPlayer sp = event.serverPlayer();
|
||||
sp.sendSystemMessage(Component.literal(
|
||||
I18nManager.getDmccTranslation("linking.player_join.not_linked")));
|
||||
sp.sendSystemMessage(Component.literal(
|
||||
I18nManager.getDmccTranslation("linking.player_join.code_hint", code)));
|
||||
sp.sendSystemMessage(buildClickableCode(code));
|
||||
sp.sendSystemMessage(buildNotLinkedMessage(code));
|
||||
}
|
||||
}
|
||||
case "multi_server_client" -> {
|
||||
|
|
@ -372,12 +368,9 @@ public class MinecraftEventHandler {
|
|||
ServerPlayer player = serverInstance.getPlayerList().getPlayer(uuid);
|
||||
if (player != null) {
|
||||
if (event.alreadyLinked()) {
|
||||
player.sendSystemMessage(Component.literal(
|
||||
I18nManager.getDmccTranslation("commands.link.already_linked", event.discordName())));
|
||||
player.sendSystemMessage(buildAlreadyLinkedMessage(event.discordName()));
|
||||
} else if (event.code() != null) {
|
||||
player.sendSystemMessage(Component.literal(
|
||||
I18nManager.getDmccTranslation("commands.link.code_generated", event.code())));
|
||||
player.sendSystemMessage(buildClickableCode(event.code()));
|
||||
player.sendSystemMessage(buildCodeGeneratedMessage(event.code()));
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
|
@ -542,21 +535,88 @@ public class MinecraftEventHandler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Builds a clickable Component for a verification code.
|
||||
* When clicked, the code is copied to the player's clipboard.
|
||||
* Builds a rich Component for the "not linked" player join notification.
|
||||
* Contains inline clickable elements: [/link code: CODE] for copy-to-clipboard
|
||||
* and [/dmcc link] for suggest-command.
|
||||
*
|
||||
* @param code The verification code.
|
||||
* @return A rich Component with inline clickable elements.
|
||||
*/
|
||||
private static Component buildNotLinkedMessage(String code) {
|
||||
return Component.empty()
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("linking.player_join.not_linked_prefix")))
|
||||
.append(buildCopyToClipboard(I18nManager.getDmccTranslation("linking.player_join.not_linked_discord_cmd", code)))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("linking.player_join.not_linked_middle")))
|
||||
.append(buildSuggestCommand("/dmcc link", I18nManager.getDmccTranslation("linking.player_join.not_linked_refresh_cmd")))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("linking.player_join.not_linked_refresh_tail")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a rich Component for the "code generated" response from /dmcc link.
|
||||
* Contains inline clickable elements: [/link code: CODE] for copy-to-clipboard
|
||||
* and [/dmcc link] for suggest-command.
|
||||
*
|
||||
* @param code The verification code.
|
||||
* @return A rich Component with inline clickable elements.
|
||||
*/
|
||||
private static Component buildCodeGeneratedMessage(String code) {
|
||||
return Component.empty()
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.code_generated_prefix")))
|
||||
.append(buildCopyToClipboard(code))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.code_generated_suffix")))
|
||||
.append(buildCopyToClipboard(I18nManager.getDmccTranslation("commands.link.code_generated_discord_cmd", code)))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.code_generated_tail")))
|
||||
.append(buildSuggestCommand("/dmcc link", I18nManager.getDmccTranslation("commands.link.code_generated_refresh_cmd")))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.code_generated_refresh_tail")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a rich Component for the "already linked" response from /dmcc link.
|
||||
* Contains an inline clickable [/dmcc unlink] suggest-command element.
|
||||
*
|
||||
* @param discordName The Discord user's display name.
|
||||
* @return A rich Component with inline clickable element.
|
||||
*/
|
||||
private static Component buildAlreadyLinkedMessage(String discordName) {
|
||||
return Component.empty()
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.already_linked", discordName)))
|
||||
.append(buildSuggestCommand("/dmcc unlink", I18nManager.getDmccTranslation("commands.link.already_linked_unlink")))
|
||||
.append(Component.literal(I18nManager.getDmccTranslation("commands.link.already_linked_suffix")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a clickable Component that copies the given text to clipboard when clicked.
|
||||
* Displayed in green bold with brackets.
|
||||
*
|
||||
* @param text The text to copy and display.
|
||||
* @return A clickable Component.
|
||||
*/
|
||||
private static Component buildClickableCode(String code) {
|
||||
return Component.literal("[" + code + "]").withStyle(style -> style
|
||||
.withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, code))
|
||||
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
private static Component buildCopyToClipboard(String text) {
|
||||
return Component.literal("[" + text + "]").withStyle(style -> style
|
||||
.withClickEvent(new ClickEvent.CopyToClipboard(text))
|
||||
.withHoverEvent(new HoverEvent.ShowText(
|
||||
Component.literal(I18nManager.getDmccTranslation("linking.player_join.click_to_copy"))))
|
||||
.withColor(ChatFormatting.GREEN)
|
||||
.withBold(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a clickable Component that suggests a command (fills the chat input) when clicked.
|
||||
* Displayed in green bold with brackets.
|
||||
*
|
||||
* @param command The command to suggest (fill into chat input).
|
||||
* @param displayText The text to display in the bracket.
|
||||
* @return A clickable Component.
|
||||
*/
|
||||
private static Component buildSuggestCommand(String command, String displayText) {
|
||||
return Component.literal("[" + displayText + "]").withStyle(style -> style
|
||||
.withClickEvent(new ClickEvent.SuggestCommand(command))
|
||||
.withHoverEvent(new HoverEvent.ShowText(
|
||||
Component.literal(I18nManager.getDmccTranslation("linking.player_join.click_to_run"))))
|
||||
.withColor(ChatFormatting.GREEN)
|
||||
.withBold(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an InfoResponsePacket containing real-time metrics of the Minecraft server.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue