From 1945cf2e194d232c33781db08dd531c6f3dcbf8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:42:26 +0000 Subject: [PATCH] Fix compilation errors, NPE, help visibility, auto-complete, click-to-copy, file path rename Co-authored-by: Xujiayao <58985541+Xujiayao@users.noreply.github.com> --- README.md | 8 +-- .../discord_mc_chat/commands/Command.java | 13 +++++ .../commands/CommandAutoCompleter.java | 4 ++ .../commands/impl/LinkCommand.java | 17 ++++-- .../commands/impl/UnlinkCommand.java | 14 ++++- .../server/linking/LinkedAccountManager.java | 10 ++-- core/src/main/resources/lang/en_us.yml | 1 + core/src/main/resources/lang/zh_cn.yml | 1 + .../events/MinecraftEventHandler.java | 53 +++++++++++++------ 9 files changed, 93 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 5d321442..76221dec 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ DMCC 所有运行模式都基于一个统一的通信模型,该模型包含两 - **一个 Discord 账户可关联多个 Minecraft 账户**(方便玩家管理大号与小号)。 - **一个 Minecraft 账户只能关联一个 Discord 账户**(确保游戏内身份的绝对唯一性)。 -- **数据持久化**: 绑定关系作为永久数据存储在 `Server` 端的 `account_linking/linked_accounts.json` 中,以 Discord ID 为主键。 -- **存储约束**: `linked_accounts.json` 中仅存储绑定关系最小必要字段(Discord ID、Minecraft UUID、添加时间),**不得额外存储** +- **数据持久化**: 绑定关系作为永久数据存储在 `Server` 端的 `account_linking/links.json` 中,以 Discord ID 为主键。 +- **存储约束**: `links.json` 中仅存储绑定关系最小必要字段(Discord ID、Minecraft UUID、添加时间),**不得额外存储** Discord 用户名或 Minecraft 玩家名;显示名称在查询时实时解析。 ### 4.2 安全绑定工作流 (严格的 MC 优先原则) @@ -75,7 +75,7 @@ DMCC 所有运行模式都基于一个统一的通信模型,该模型包含两 - 若该玩家存在未过期验证码,返回同一验证码并将过期时间重置为“当前时间 + 5 分钟”; - 若验证码已过期或不存在,生成并返回新验证码(同样 5 分钟有效)。 4. **确认所有权**: 玩家前往 Discord,使用斜杠命令 `/link A7X9P2`。 -5. **完成绑定**: Server 验证代码有效后,将该 Discord ID 与 MC UUID 写入 `linked_accounts.json`,并使该验证码失效。 +5. **完成绑定**: Server 验证代码有效后,将该 Discord ID 与 MC UUID 写入 `links.json`,并使该验证码失效。 ### 4.3 解绑与查询工作流 @@ -138,7 +138,7 @@ Discord 用户的身份将通过以下规则在 Server 端结算为一个具体 1. **全量同步原则**: 每次同步均执行“全量重算 + 全量应用”,而非增量补丁。 2. **强制覆盖原则**: DMCC 将重置 Minecraft 服务器当前 OP 列表,并依据 DMCC 配置中的映射规则重新分配。 -3. **绑定缺失回退**: 若某玩家解除绑定后在 `linked_accounts.json` 中不再出现,则在下一次全量同步中该玩家 OP 等级将被重置为 0。 +3. **绑定缺失回退**: 若某玩家解除绑定后在 `links.json` 中不再出现,则在下一次全量同步中该玩家 OP 等级将被重置为 0。 4. **关闭时不干预**: 若 `sync_op_level_to_minecraft=false`,解绑不触发 OP 回收,服务器维持原样。 5. **与原生 `/op` 的关系**: 开启后,管理员在游戏内使用原生命令 `/op` 手动授予的结果会在下一次 DMCC 全量同步时被覆盖,这是预期行为。 diff --git a/core/src/main/java/com/xujiayao/discord_mc_chat/commands/Command.java b/core/src/main/java/com/xujiayao/discord_mc_chat/commands/Command.java index 12b22d79..ac074c8b 100644 --- a/core/src/main/java/com/xujiayao/discord_mc_chat/commands/Command.java +++ b/core/src/main/java/com/xujiayao/discord_mc_chat/commands/Command.java @@ -53,6 +53,19 @@ public interface Command { return false; } + /** + * Whether this command should appear in auto-complete suggestions for the execute command. + *
+ * Commands that are only meaningful for specific sender types (e.g., link/unlink + * which require player or Discord context) should return false. + * This does NOT affect whether the command can be executed — only its auto-complete visibility. + * + * @return true if this command should appear in auto-complete, false otherwise. + */ + default boolean isAutoCompletable() { + return true; + } + /** * Whether this command should be visible in help listings for the given sender. *
diff --git a/core/src/main/java/com/xujiayao/discord_mc_chat/commands/CommandAutoCompleter.java b/core/src/main/java/com/xujiayao/discord_mc_chat/commands/CommandAutoCompleter.java
index f046519e..3eabfe0f 100644
--- a/core/src/main/java/com/xujiayao/discord_mc_chat/commands/CommandAutoCompleter.java
+++ b/core/src/main/java/com/xujiayao/discord_mc_chat/commands/CommandAutoCompleter.java
@@ -68,6 +68,7 @@ public class CommandAutoCompleter {
CommandManager.getCommands().stream()
.filter(cmd -> cmd.name().startsWith(commandName))
+ .filter(Command::isAutoCompletable)
.sorted(Comparator.comparing(Command::name))
.forEach(cmd -> addCommandIfAuthorized(cmd, opLevel, suggestions));
@@ -308,6 +309,9 @@ public class CommandAutoCompleter {
* @param suggestions The list to append to.
*/
private static void addCommandIfAuthorized(Command cmd, int opLevel, List
* One Discord account can link multiple Minecraft accounts (1:N).
* One Minecraft account can only link to one Discord account (N:1 uniqueness).
- * Data is stored in {@code account_linking/linked_accounts.json} in the DMCC config directory.
+ * Data is stored in {@code account_linking/links.json} in the DMCC config directory.
*
* @author Xujiayao
*/
public class LinkedAccountManager {
- private static final Path LINKS_FILE = Paths.get("./config/discord_mc_chat/account_linking/linked_accounts.json");
+ private static final Path LINKS_FILE = Paths.get("./config/discord_mc_chat/account_linking/links.json");
private static final ConcurrentHashMap
* This intentionally does NOT save to disk, so users can manually edit
- * {@code linked_accounts.json} while DMCC is running and reload to apply their changes.
+ * {@code links.json} while DMCC is running and reload to apply their changes.
* Any in-memory changes that were not yet persisted via {@link #save()} will be lost.
* In practice, all mutations (link/unlink) call {@link #save()} immediately,
* so no data is lost under normal operation.
@@ -195,6 +195,10 @@ public class LinkedAccountManager {
* @return The Discord user ID that was unlinked from, or null if the UUID was not linked.
*/
public static synchronized String unlinkByMinecraftUuid(String minecraftUuid, String minecraftName) {
+ if (minecraftUuid == null) {
+ return null;
+ }
+
String discordId = UUID_TO_DISCORD.remove(minecraftUuid);
if (discordId == null) {
return null;
diff --git a/core/src/main/resources/lang/en_us.yml b/core/src/main/resources/lang/en_us.yml
index d7764747..f9bd5411 100644
--- a/core/src/main/resources/lang/en_us.yml
+++ b/core/src/main/resources/lang/en_us.yml
@@ -273,6 +273,7 @@ linking:
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"
minecraft:
translations:
diff --git a/core/src/main/resources/lang/zh_cn.yml b/core/src/main/resources/lang/zh_cn.yml
index 07dd35d3..55efc7ab 100644
--- a/core/src/main/resources/lang/zh_cn.yml
+++ b/core/src/main/resources/lang/zh_cn.yml
@@ -273,6 +273,7 @@ linking:
player_join:
not_linked: "你的 Minecraft 账户尚未绑定 Discord。使用 /dmcc link 获取验证码。"
code_hint: "在 5 分钟内在 Discord 上使用 /link {} 来绑定你的账户。"
+ click_to_copy: "点击复制验证码"
minecraft:
translations:
diff --git a/minecraft/src/main/java/com/xujiayao/discord_mc_chat/minecraft/events/MinecraftEventHandler.java b/minecraft/src/main/java/com/xujiayao/discord_mc_chat/minecraft/events/MinecraftEventHandler.java
index 4785d102..bf7f94ab 100644
--- a/minecraft/src/main/java/com/xujiayao/discord_mc_chat/minecraft/events/MinecraftEventHandler.java
+++ b/minecraft/src/main/java/com/xujiayao/discord_mc_chat/minecraft/events/MinecraftEventHandler.java
@@ -23,16 +23,22 @@ import com.xujiayao.discord_mc_chat.utils.events.CoreEvents;
import com.xujiayao.discord_mc_chat.utils.events.EventManager;
import com.xujiayao.discord_mc_chat.utils.i18n.I18nManager;
import net.minecraft.advancements.DisplayInfo;
+import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.HoverEvent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTickRateManager;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.NameAndId;
+import net.minecraft.server.players.PlayerList;
+import net.minecraft.server.players.ServerOpList;
+import net.minecraft.server.players.ServerOpListEntry;
import net.minecraft.stats.StatType;
import net.minecraft.util.TimeUtil;
import net.minecraft.world.level.GameRules;
@@ -162,6 +168,7 @@ public class MinecraftEventHandler {
I18nManager.getDmccTranslation("linking.player_join.not_linked")));
sp.sendSystemMessage(Component.literal(
I18nManager.getDmccTranslation("linking.player_join.code_hint", code)));
+ sp.sendSystemMessage(buildClickableCode(code));
}
}
case "multi_server_client" -> {
@@ -369,7 +376,8 @@ public class MinecraftEventHandler {
I18nManager.getDmccTranslation("commands.link.already_linked", event.discordName())));
} else if (event.code() != null) {
player.sendSystemMessage(Component.literal(
- I18nManager.getDmccTranslation("linking.player_join.code_hint", event.code())));
+ I18nManager.getDmccTranslation("commands.link.code_generated", event.code())));
+ player.sendSystemMessage(buildClickableCode(event.code()));
}
}
} catch (Exception ignored) {
@@ -405,17 +413,16 @@ public class MinecraftEventHandler {
serverInstance.execute(() -> {
try {
- net.minecraft.server.players.PlayerList playerList = serverInstance.getPlayerList();
- net.minecraft.server.players.ServerOpList opList = playerList.getOps();
+ PlayerList playerList = serverInstance.getPlayerList();
+ ServerOpList opList = playerList.getOps();
// Step 1: De-op all currently opped players
// Copy the list to avoid ConcurrentModificationException
- List