From a3b2e7dd2791e439959eab3ec70789b3ec344d6b Mon Sep 17 00:00:00 2001 From: marcos Date: Thu, 6 Aug 2026 03:54:01 +0000 Subject: [PATCH] fix(ia): wire canalhandia.ia.privado gate, tighten subcommand hijack Review of Task 11 (commit 5f616d3) flagged a blocking spec regression: canalhandia.ia.privado was declared (default: true) but never checked -- ia(sender, args, isPrivate) only checked canalhandia.ia, so /iap silently required canalhandia.ia and the privado perm did nothing. Per operator decision, /ia and /iap are now separate gates, both default op, so LuckPerms can grant them independently (operator + permitted only, not everyone). /ia needs canalhandia.ia; /iap needs canalhandia.ia.privado. Also fixes three should-fix findings: - Subcommand hijack is no longer greedy: perfil and feedback only hijack when the second token is one they act on (a known profile key, or "ruim"), so "/ia perfil do servidor" and "/ia feedback do mapa?" fall through and are asked. corrigir stays greedy (a correction always reads the rest). - AiProfile.isValid tells a real key from the PRECISO fallback, so /ia perfil blah no longer silently switches to PRECISO. Co-Authored-By: Claude --- .../marcospaulo/canalhandia/AiProfile.java | 13 +++++++ .../canalhandia/CanalhandiaCommand.java | 36 +++++++++++++------ src/main/resources/plugin.yml | 4 +-- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/java/dev/marcospaulo/canalhandia/AiProfile.java b/src/main/java/dev/marcospaulo/canalhandia/AiProfile.java index 4ff3906..07f6569 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/AiProfile.java +++ b/src/main/java/dev/marcospaulo/canalhandia/AiProfile.java @@ -22,4 +22,17 @@ enum AiProfile { } return PRECISO; } + + /** True if {@code key} names a profile. Use before {@link #byKey} to tell a + * real key from the {@code PRECISO} fallback, so {@code /ia perfil blah} + * can be rejected (or fall through to a question) rather than silently + * switching to PRECISO. */ + static boolean isValid(String key) { + for (AiProfile profile : values()) { + if (profile.name().equalsIgnoreCase(key)) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java index c43ddff..d09255c 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java +++ b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java @@ -759,7 +759,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { * {@code lp user permission set canalhandia.ia true}. */ private boolean ia(CommandSender sender, String[] args, boolean isPrivate) { - if (!sender.hasPermission("canalhandia.ia")) { + // /ia and /iap are separate gates so LuckPerms can grant them + // independently — an operator can let someone ask privately without + // letting them spam public chat, or vice-versa. Both default to op, + // matching the "operator + LuckPerms-permitted only" rule. + String perm = isPrivate ? "canalhandia.ia.privado" : "canalhandia.ia"; + if (!sender.hasPermission(perm)) { return denied(sender); } if (!plugin.settings().moduleEnabled(Module.IA)) { @@ -770,18 +775,27 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { Msg.error(sender, "Só jogadores podem usar /ia."); return true; } - // Subcommands come before the question. Tradeoff: a question that - // literally starts with "perfil", "corrigir" or "feedback" is hijacked - // — e.g. "/ia perfil do servidor" runs /ia perfil with no profile name - // and shows usage instead of asking. Acceptable on a small server, - // where the operator commands matter more than the edge case. + // Subcommands come before the question. They only hijack when the + // second token is one they would actually act on, so a real question + // like "/ia perfil do servidor" or "/ia feedback do mapa?" falls + // through and is asked. "corrigir" stays greedy — a correction always + // reads the rest of the line, and "/ia corrigir ..." is never a natural + // question. if (args.length > 0) { - switch (args[0].toLowerCase(Locale.ROOT)) { - case "perfil" -> { iaProfile(sender, args); return true; } - case "corrigir" -> { iaCorrect(sender, args); return true; } - case "feedback" -> { iaFeedback(sender, args); return true; } - default -> { /* fall through: treat all args as the question */ } + String sub = args[0].toLowerCase(Locale.ROOT); + if (sub.equals("perfil") && (args.length == 1 || AiProfile.isValid(args[1]))) { + iaProfile(sender, args); + return true; } + if (sub.equals("corrigir")) { + iaCorrect(sender, args); + return true; + } + if (sub.equals("feedback") && args.length >= 2 && args[1].equalsIgnoreCase("ruim")) { + iaFeedback(sender, args); + return true; + } + // default: treat all args as the question } if (args.length == 0) { Msg.error(sender, isPrivate ? "Uso: /iap " : "Uso: /ia "); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 916a7f8..056cfdd 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -90,8 +90,8 @@ permissions: description: Permite usar /ia. Padrão op; o LuckPerms pode conceder a outros. default: op canalhandia.ia.privado: - description: Permite perguntar em privado com /iap. - default: true + description: Permite perguntar em privado com /iap. Padrão op; o LuckPerms pode conceder a outros. + default: op canalhandia.ia.corrigir: description: Permite registrar correções para as respostas da IA. default: op