Commit 06f910d2 by Jonathan Thomas

New whitelist and blacklist Minecraft commands, to show and hide chat bubbles based on entity type

parent eb9872b2
Pipeline #12656 passed with stages
in 2 minutes 27 seconds
...@@ -6,8 +6,12 @@ All notable changes to **CreatureChat** are documented in this file. The format ...@@ -6,8 +6,12 @@ All notable changes to **CreatureChat** are documented in this file. The format
## Unreleased ## Unreleased
### Added
- New whitelist and blacklist Minecraft commands, to show and hide chat bubbles based on entity type
### Changed ### Changed
- Fixed CurseForge deploy script to be much faster, and correctly lookup valid Type and Version IDs - Fixed CurseForge deploy script to be much faster, and correctly lookup valid Type and Version IDs
- Large refactor of Minecraft commands (and how --config args are parsed)
## [1.0.7] - 2024-07-03 ## [1.0.7] - 2024-07-03
......
...@@ -14,6 +14,8 @@ import java.io.Writer; ...@@ -14,6 +14,8 @@ import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/** /**
* The {@code ConfigurationHandler} class loads and saves configuration settings for this mod. It first * The {@code ConfigurationHandler} class loads and saves configuration settings for this mod. It first
...@@ -69,6 +71,8 @@ public class ConfigurationHandler { ...@@ -69,6 +71,8 @@ public class ConfigurationHandler {
private int maxOutputTokens = 200; private int maxOutputTokens = 200;
private double percentOfContext = 0.75; private double percentOfContext = 0.75;
private int timeout = 10; private int timeout = 10;
private List<String> whitelist = new ArrayList<>();
private List<String> blacklist = new ArrayList<>();
// Getters and setters for existing fields // Getters and setters for existing fields
public String getApiKey() { return apiKey; } public String getApiKey() { return apiKey; }
...@@ -77,7 +81,7 @@ public class ConfigurationHandler { ...@@ -77,7 +81,7 @@ public class ConfigurationHandler {
// Update URL if a CreatureChat API key is detected // Update URL if a CreatureChat API key is detected
setUrl("https://api.creaturechat.com/v1/chat/completions"); setUrl("https://api.creaturechat.com/v1/chat/completions");
} else if (apiKey.startsWith("sk-")) { } else if (apiKey.startsWith("sk-")) {
// Update URL if a OpenAI API key is detected // Update URL if an OpenAI API key is detected
setUrl("https://api.openai.com/v1/chat/completions"); setUrl("https://api.openai.com/v1/chat/completions");
} }
this.apiKey = apiKey; this.apiKey = apiKey;
...@@ -92,7 +96,6 @@ public class ConfigurationHandler { ...@@ -92,7 +96,6 @@ public class ConfigurationHandler {
public int getTimeout() { return timeout; } public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; } public void setTimeout(int timeout) { this.timeout = timeout; }
// Getters and setters for new fields
public int getMaxContextTokens() { return maxContextTokens; } public int getMaxContextTokens() { return maxContextTokens; }
public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; } public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; }
...@@ -102,5 +105,10 @@ public class ConfigurationHandler { ...@@ -102,5 +105,10 @@ public class ConfigurationHandler {
public double getPercentOfContext() { return percentOfContext; } public double getPercentOfContext() { return percentOfContext; }
public void setPercentOfContext(double percentOfContext) { this.percentOfContext = percentOfContext; } public void setPercentOfContext(double percentOfContext) { this.percentOfContext = percentOfContext; }
public List<String> getWhitelist() { return whitelist; }
public void setWhitelist(List<String> whitelist) { this.whitelist = whitelist; }
public List<String> getBlacklist() { return blacklist; }
public void setBlacklist(List<String> blacklist) { this.blacklist = blacklist; }
} }
} }
...@@ -5,14 +5,26 @@ import com.mojang.brigadier.arguments.ArgumentType; ...@@ -5,14 +5,26 @@ import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.registry.Registries;
import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* The {@code CreatureChatCommands} class registers custom commands to set new API key, model, and url. * The {@code CreatureChatCommands} class registers custom commands to set new API key, model, and url.
* Permission level set to 4 (server owner), since this deals with API keys and potential costs. * Permission level set to 4 (server owner), since this deals with API keys and potential costs.
...@@ -33,6 +45,8 @@ public class CreatureChatCommands { ...@@ -33,6 +45,8 @@ public class CreatureChatCommands {
.then(registerSetCommand("url", "URL", StringArgumentType.string())) .then(registerSetCommand("url", "URL", StringArgumentType.string()))
.then(registerSetCommand("model", "Model", StringArgumentType.string())) .then(registerSetCommand("model", "Model", StringArgumentType.string()))
.then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer())) .then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer()))
.then(registerWhitelistCommand())
.then(registerBlacklistCommand())
.then(registerHelpCommand())); .then(registerHelpCommand()));
} }
...@@ -41,26 +55,13 @@ public class CreatureChatCommands { ...@@ -41,26 +55,13 @@ public class CreatureChatCommands {
.requires(source -> source.hasPermissionLevel(4)) .requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set") .then(CommandManager.literal("set")
.then(CommandManager.argument("value", valueType) .then(CommandManager.argument("value", valueType)
.then(CommandManager.literal("--config") .then(addConfigArgs((context, useServerConfig) -> {
.then(CommandManager.literal("default")
.executes(context -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), false, settingDescription);
return 1;
})
)
.then(CommandManager.literal("server")
.executes(context -> {
if (valueType instanceof StringArgumentType) if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), true, settingDescription); return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), useServerConfig, settingDescription);
else if (valueType instanceof IntegerArgumentType) else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), true, settingDescription); return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), useServerConfig, settingDescription);
return 1; return 1;
}) }))
)
)
.executes(context -> { .executes(context -> {
if (valueType instanceof StringArgumentType) if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription); return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
...@@ -71,6 +72,55 @@ public class CreatureChatCommands { ...@@ -71,6 +72,55 @@ public class CreatureChatCommands {
)); ));
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerWhitelistCommand() {
return CommandManager.literal("whitelist")
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.argument("entityType", IdentifierArgumentType.identifier())
.suggests((context, builder) -> CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder))
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), useServerConfig)))
.executes(context -> modifyList(context, "whitelist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), false)))
.then(CommandManager.literal("all")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", "all", useServerConfig)))
.executes(context -> modifyList(context, "whitelist", "all", false)))
.then(CommandManager.literal("clear")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "whitelist", "clear", useServerConfig)))
.executes(context -> modifyList(context, "whitelist", "clear", false)));
}
private static LiteralArgumentBuilder<ServerCommandSource> registerBlacklistCommand() {
return CommandManager.literal("blacklist")
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.argument("entityType", IdentifierArgumentType.identifier())
.suggests((context, builder) -> CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder))
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), useServerConfig)))
.executes(context -> modifyList(context, "blacklist", IdentifierArgumentType.getIdentifier(context, "entityType").toString(), false)))
.then(CommandManager.literal("all")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", "all", useServerConfig)))
.executes(context -> modifyList(context, "blacklist", "all", false)))
.then(CommandManager.literal("clear")
.then(addConfigArgs((context, useServerConfig) -> modifyList(context, "blacklist", "clear", useServerConfig)))
.executes(context -> modifyList(context, "blacklist", "clear", false)));
}
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() {
return CommandManager.literal("help")
.executes(context -> {
String helpMessage = "Usage of CreatureChat Commands:\n"
+ "/creaturechat key set <key> - Sets the API key\n"
+ "/creaturechat url set \"<url>\" - Sets the URL\n"
+ "/creaturechat model set <model> - Sets the model\n"
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n"
+ "/creaturechat whitelist <entityType | all | clear> - Show chat bubbles\n"
+ "/creaturechat blacklist <entityType | all | clear> - Hide chat bubbles\n"
+ "\n"
+ "Optional: Append [--config default | server] to any command to specify configuration scope.\n"
+ "\n"
+ "Security: Level 4 permission required.";
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false);
return 1;
});
}
private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) { private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) {
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer()); ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig(); ConfigurationHandler.Config config = configHandler.loadConfig();
...@@ -109,13 +159,11 @@ public class CreatureChatCommands { ...@@ -109,13 +159,11 @@ public class CreatureChatCommands {
Text feedbackMessage; Text feedbackMessage;
if (configHandler.saveConfig(config, useServerConfig)) { if (configHandler.saveConfig(config, useServerConfig)) {
// succeeded
feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN); feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false); source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString()); LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1; return 1;
} else { } else {
// failed
feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED); feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false); source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString()); LOGGER.info("Command executed: " + feedbackMessage.getString());
...@@ -123,20 +171,84 @@ public class CreatureChatCommands { ...@@ -123,20 +171,84 @@ public class CreatureChatCommands {
} }
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() { private static int modifyList(CommandContext<ServerCommandSource> context, String listName, String action, boolean useServerConfig) {
return CommandManager.literal("help") ServerCommandSource source = context.getSource();
.executes(context -> { ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
String helpMessage = "Usage of CreatureChat Commands:\n" ConfigurationHandler.Config config = configHandler.loadConfig();
+ "/creaturechat key set <key> - Sets the API key\n" List<String> entityTypes = Registries.ENTITY_TYPE.stream()
+ "/creaturechat url set \"<url>\" - Sets the URL\n" .filter(entityType -> LivingEntity.class.isAssignableFrom(entityType.getBaseClass()))
+ "/creaturechat model set <model> - Sets the model\n" .map(EntityType::getId)
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n" .map(Identifier::toString)
+ "\n" .collect(Collectors.toList());
+ "Optional: Append [--config default | server] to any command to specify configuration scope.\n"
+ "\n" try {
+ "Security: Level 4 permission required."; if ("all".equals(action)) {
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false); if ("whitelist".equals(listName)) {
config.setWhitelist(entityTypes);
config.setBlacklist(new ArrayList<>()); // Clear blacklist
} else if ("blacklist".equals(listName)) {
config.setBlacklist(entityTypes);
config.setWhitelist(new ArrayList<>()); // Clear whitelist
}
} else if ("clear".equals(action)) {
if ("whitelist".equals(listName)) {
config.setWhitelist(new ArrayList<>());
} else if ("blacklist".equals(listName)) {
config.setBlacklist(new ArrayList<>());
}
} else {
if (!entityTypes.contains(action)) {
throw new IllegalArgumentException("Invalid entity type: " + action);
}
if ("whitelist".equals(listName)) {
List<String> whitelist = new ArrayList<>(config.getWhitelist());
if (!whitelist.contains(action)) {
whitelist.add(action);
config.setWhitelist(whitelist);
}
// Remove from blacklist if present
List<String> blacklist = new ArrayList<>(config.getBlacklist());
blacklist.remove(action);
config.setBlacklist(blacklist);
} else if ("blacklist".equals(listName)) {
List<String> blacklist = new ArrayList<>(config.getBlacklist());
if (!blacklist.contains(action)) {
blacklist.add(action);
config.setBlacklist(blacklist);
}
// Remove from whitelist if present
List<String> whitelist = new ArrayList<>(config.getWhitelist());
whitelist.remove(action);
config.setWhitelist(whitelist);
}
}
} catch (IllegalArgumentException e) {
Text errorMessage = Text.literal(e.getMessage()).formatted(Formatting.RED);
source.sendFeedback(() -> errorMessage, false);
LOGGER.error("Error modifying list: " + e.getMessage(), e);
return 0;
}
if (configHandler.saveConfig(config, useServerConfig)) {
Text feedbackMessage = Text.literal("Successfully updated " + listName + " with " + action).formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false);
return 1; return 1;
}); } else {
Text feedbackMessage = Text.literal("Failed to update " + listName).formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false);
return 0;
}
}
private static LiteralArgumentBuilder<ServerCommandSource> addConfigArgs(CommandExecutor executor) {
return CommandManager.literal("--config")
.then(CommandManager.literal("default").executes(context -> executor.run(context, false)))
.then(CommandManager.literal("server").executes(context -> executor.run(context, true)))
.executes(context -> executor.run(context, false));
}
@FunctionalInterface
private interface CommandExecutor {
int run(CommandContext<ServerCommandSource> context, boolean useServerConfig) throws CommandSyntaxException;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment