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 <noreply@anthropic.com>
This commit is contained in:
marcos
2026-08-06 03:54:01 +00:00
parent 5f616d3d99
commit a3b2e7dd27
3 changed files with 40 additions and 13 deletions
@@ -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;
}
}
@@ -759,7 +759,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
* {@code lp user <nome> 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 <pergunta>" : "Uso: /ia <pergunta>");
+2 -2
View File
@@ -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