Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b6ec088
removed: todo markdown file
marioogg Mar 29, 2026
44d267d
docs: fixed indentation
marioogg Mar 29, 2026
b0227d9
feat: added customizable error messages for bukkit
marioogg Mar 29, 2026
dee5f09
fix: added setters for the Bukkit error messages
marioogg Mar 29, 2026
ad817d5
feat: added customizable error messages for bungee
marioogg Mar 29, 2026
ecd502c
fix: changed the import from CraftBukkit to BungeeCord chatcolor
marioogg Mar 29, 2026
96cae0b
feat: added customizable error messages for velocity
marioogg Mar 29, 2026
78adcf4
fix: fixed hardcoded no permission messages in bungee and bukkit
marioogg Mar 29, 2026
bebeb70
feat: added cooldown system
marioogg Mar 29, 2026
1a44e20
feat: implemented cooldown into Bukkit
marioogg Mar 29, 2026
81d6226
feat: implemented cooldown into Bungee
marioogg Mar 29, 2026
29ce9ca
feat: implemented cooldown into Velocity
marioogg Mar 29, 2026
59c8100
chore: bump version to 2.0-b1
marioogg Mar 29, 2026
ae3361c
chore: update publish.yml and build.gradle for version 2.0 for CI tes…
marioogg Mar 29, 2026
7b0a9bc
chore: update .gitignore to include command.iml and /.todo/
marioogg Mar 29, 2026
d5d5117
chore (docs): added proper automatic repo selection
marioogg Apr 6, 2026
d82d599
feat: added validation parameter system (min, max and regex match)
marioogg Apr 6, 2026
1fa0563
feat: implemented validation on bukkit
marioogg Apr 6, 2026
388af29
feat: implemented validation on bungee
marioogg Apr 6, 2026
9276dc8
fix: changed wrong module class usage from BungeeParamProcessor.java
marioogg Apr 6, 2026
756bdeb
feat: implemented validation on velocity
marioogg Apr 6, 2026
0dce82c
fix: referencing the Min message wrongly
marioogg Apr 6, 2026
a74d53e
chore (build): bump ver to 2.0-b2
marioogg Apr 6, 2026
a477f46
fix (build): syntax error
marioogg Apr 6, 2026
bed67e7
feat: add release drafter configuration for automated release notes
marioogg Apr 6, 2026
5b1e8b4
feat: add release drafter configuration for automated release notes
marioogg Apr 6, 2026
dc2ed3f
feat: add release drafter configuration for automated release notes
marioogg Apr 6, 2026
4ec4f00
Merge branch 'master' into ver/2.0
marioogg Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master
- ver/2.0

jobs:
publish:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ build/
target/
qodana.yaml
todo.md
command.iml
command.iml
/.todo/
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# MC Command Framework



### DISCLAIMER:
I did not make nor helped in the initial development of this project. All the credit goes to [ashtton](https://github.com/ashtton) and his [original project](https://github.com/ashtton/spigot-command-api).
What I'll actually do is maintain this plugin updated and according to my needs, while keeping it public, as far as providing builds on my Maven repository (check below).
Expand Down
15 changes: 9 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group 'me.marioogg'
version '1.2.1'
version '2.0-b2'

repositories {
maven { url 'https://maven.marioogg.dev/repository/public/' }
Expand All @@ -22,11 +22,15 @@ publishing {

repositories {
maven {
def isSnapshot = version.toString().endsWith("SNAPSHOT")
def releasesRepoUrl = "https://maven.marioogg.dev/repository/releases"
def snapshotsRepoUrl = "https://maven.marioogg.dev/repository/snapshots"
def versionString = version.toString()
def isBeta = versionString.contains("-b")
def isSnapshot = versionString.endsWith("SNAPSHOT")

url = uri(isSnapshot ? snapshotsRepoUrl : releasesRepoUrl)
def privateRepo = "https://maven.marioogg.dev/repository/private"
def snapshotRepo = "https://maven.marioogg.dev/repository/snapshots"
def releaseRepoUrl = "https://maven.marioogg.dev/repository/releases"

url = uri(isBeta ? privateRepo : isSnapshot ? snapshotRepo : releaseRepoUrl)

credentials {
username = System.getenv("MAVEN_USERNAME")
Expand All @@ -35,7 +39,6 @@ publishing {
}
}
}

dependencies {
compileOnly('org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT') {
exclude group: 'net.md-5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public boolean execute(CommandSender sender, String label, String[] args) {
HelpNode helpNode = node.getHelpNodes().get(0);

if(!helpNode.getPermission().isEmpty() && !sender.hasPermission(helpNode.getPermission())) {
sender.sendMessage(ChatColor.RED + "I'm sorry, although you do not have permission to execute this command.");
sender.sendMessage(BukkitCommandHandler.getNoPermissionMessage());
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.reflect.ClassPath;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.marioogg.command.Command;
import me.marioogg.command.Subcommand;
Expand All @@ -10,6 +11,7 @@
import me.marioogg.command.bukkit.node.CommandNode;
import me.marioogg.command.bukkit.parameter.ParamProcessor;
import me.marioogg.command.bukkit.parameter.Processor;
import org.bukkit.ChatColor;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -26,6 +28,16 @@ public class BukkitCommandHandler {
@Getter
private static Logger logger;

@Getter @Setter private static String noPermissionMessage = ChatColor.RED + "I'm sorry, but you do not have permission to perform this command.";
@Getter @Setter private static String playerOnlyMessage = ChatColor.RED + "You must be a player to execute this command.";
@Getter @Setter private static String consoleOnlyMessage = ChatColor.RED + "This command can only be executed by console.";
@Getter @Setter private static String internalErrorMessage = ChatColor.RED + "An internal error occurred while executing this command.";
@Getter @Setter private static String cooldownMessage = ChatColor.RED + "You must wait {seconds} more second(s) before using this command again.";
@Getter @Setter private static String minValidationMessage = ChatColor.RED + "The value must be at least {min}.";
@Getter @Setter private static String maxValidationMessage = ChatColor.RED + "The value must be at most {max}.";
@Getter @Setter private static String matchesValidationMessage = ChatColor.RED + "Invalid format.";


public static void setPlugin(Plugin plugin) {
BukkitCommandHandler.plugin = plugin;
logger = LoggerFactory.getLogger(plugin.getName());
Expand Down Expand Up @@ -145,4 +157,4 @@ private static boolean isInstantiable(Class<?> clazz) {
&& Arrays.stream(clazz.getDeclaredConstructors())
.anyMatch(c -> c.getParameterCount() == 0);
}
}
}
33 changes: 26 additions & 7 deletions src/main/java/me/marioogg/command/bukkit/node/CommandNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import me.marioogg.command.Command;
import me.marioogg.command.bukkit.BukkitCommandHandler;
import me.marioogg.command.bukkit.BukkitCommand;
import me.marioogg.command.common.cooldown.Cooldown;
import me.marioogg.command.common.cooldown.CooldownManager;
import me.marioogg.command.common.cooldown.CooldownNode;
import me.marioogg.command.common.flag.Flag;
import me.marioogg.command.common.flag.FlagNode;
import me.marioogg.command.common.help.HelpNode;
Expand Down Expand Up @@ -43,6 +46,8 @@ public class CommandNode {
private final Object parentClass;
private final Method method;

private final CooldownNode cooldownNode;

private final List<ArgumentNode> parameters = new ArrayList<>();
private final List<FlagNode> flagNodes = new ArrayList<>();
private final List<HelpNode> helpNodes = new ArrayList<>();
Expand Down Expand Up @@ -76,6 +81,9 @@ public CommandNode(Object parentClass, Method method, Command command) {
flagNodes.add(new FlagNode(flag, parameter));
});

Cooldown cooldown = method.getAnnotation(Cooldown.class);
this.cooldownNode = cooldown != null ? new CooldownNode(cooldown.seconds(), cooldown.bypassPermission()) : null;

names.forEach(name -> {
if(!BukkitCommand.getCommands().containsKey(name.split(" ")[0].toLowerCase())) new BukkitCommand(name.split(" ")[0].toLowerCase());
});
Expand Down Expand Up @@ -152,12 +160,12 @@ public int getMatchProbability(CommandSender sender, String label, String[] args

public void sendUsageMessage(CommandSender sender) {
if(consoleOnly && sender instanceof Player) {
sender.sendMessage(ChatColor.RED + "This command can only be executed by console.");
sender.sendMessage(BukkitCommandHandler.getConsoleOnlyMessage());
return;
}

if(playerOnly && sender instanceof ConsoleCommandSender) {
sender.sendMessage(ChatColor.RED + "You must be a player to execute this command.");
sender.sendMessage(BukkitCommandHandler.getPlayerOnlyMessage());
return;
}

Expand All @@ -167,7 +175,7 @@ public void sendUsageMessage(CommandSender sender) {
}

if(!permission.isEmpty() && !sender.hasPermission(permission)) {
sender.sendMessage(ChatColor.RED + "I'm sorry, you do not have permission to execute this command.");
sender.sendMessage(BukkitCommandHandler.getNoPermissionMessage());
return;
}

Expand All @@ -194,20 +202,31 @@ public int requiredArgumentsLength() {

public void execute(CommandSender sender, String[] args) {
if(!permission.isEmpty() && !sender.hasPermission(permission)) {
sender.sendMessage(ChatColor.RED + "I'm sorry, but you do not have permission to perform this command.");
sender.sendMessage(BukkitCommandHandler.getNoPermissionMessage());
return;
}

if(sender instanceof ConsoleCommandSender && playerOnly) {
sender.sendMessage(ChatColor.RED + "You must be a player to execute this command.");
sender.sendMessage(BukkitCommandHandler.getPlayerOnlyMessage());
return;
}

if(sender instanceof Player && consoleOnly) {
sender.sendMessage(ChatColor.RED + "This command is only executable by console.");
sender.sendMessage(BukkitCommandHandler.getConsoleOnlyMessage());
return;
}

if (cooldownNode != null && sender instanceof Player player) {
if (cooldownNode.getBypassPermission().isEmpty() || !player.hasPermission(cooldownNode.getBypassPermission())) {
if (CooldownManager.isOnCooldown(player.getUniqueId(), names.get(0))) {
long remaining = CooldownManager.getRemainingSeconds(player.getUniqueId(), names.get(0));
sender.sendMessage(BukkitCommandHandler.getCooldownMessage().replace("{seconds}", String.valueOf(remaining)));
return;
}
CooldownManager.setCooldown(player.getUniqueId(), names.get(0), cooldownNode.getSeconds());
}
}

int nameArgs = (names.get(0).split(" ").length - 1);

Set<String> activatedFlags = new HashSet<>();
Expand Down Expand Up @@ -286,7 +305,7 @@ private void invokeMethod(CommandSender sender, List<Object> params) {
} catch (IllegalAccessException | InvocationTargetException e) {
Throwable cause = (e instanceof InvocationTargetException) ? e.getCause() : e;
log.error("An exception occurred while executing command '{}' (Sender: {})", names.get(0), sender.getName(), cause);
sender.sendMessage(ChatColor.RED + "An internal error occurred while executing this command.");
sender.sendMessage(BukkitCommandHandler.getInternalErrorMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import lombok.Data;
import lombok.Getter;
import me.marioogg.command.bukkit.BukkitCommandHandler;
import me.marioogg.command.common.duration.Duration;
import me.marioogg.command.bukkit.node.ArgumentNode;
import me.marioogg.command.bukkit.parameter.impl.*;
import me.marioogg.command.common.validation.*;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
Expand All @@ -24,6 +27,8 @@ public class ParamProcessor {
@Getter private static final HashMap<Class<?>, Processor<?>> processors = new HashMap<>();
private static boolean loaded = false;

private final Logger logger = BukkitCommandHandler.getLogger();

private final ArgumentNode node;
private final String supplied;
private final CommandSender sender;
Expand All @@ -38,7 +43,21 @@ public Object get() {
Processor<?> processor = processors.get(node.getParameter().getType());
if(processor == null) return supplied;

return processor.process(sender, supplied);
Object result = processor.process(sender, supplied);
if (result == null) return null;

ValidationResult validation = Validator.validate(node.getParameter(), result);
if (!validation.isValid()) {
if (validation instanceof Min) {
sender.sendMessage(BukkitCommandHandler.getMinValidationMessage().replace("{min}", String.valueOf(((Min) validation).value())));
} else if (validation instanceof Max) {
sender.sendMessage(BukkitCommandHandler.getMaxValidationMessage().replace("{max}", String.valueOf(((Max) validation).value())));
} else if (validation instanceof Matches) {
sender.sendMessage(BukkitCommandHandler.getMatchesValidationMessage());
}
return null;
}
return result;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void execute(CommandSender sender, String[] args) {
HelpNode helpNode = node.getHelpNodes().get(0);

if (!helpNode.getPermission().isEmpty() && !sender.hasPermission(helpNode.getPermission())) {
sender.sendMessage(new TextComponent(ChatColor.RED + "I'm sorry, although you do not have permission to execute this command."));
sender.sendMessage(new TextComponent(BungeeCommandHandler.getNoPermissionMessage()));
return;
}

Expand Down
32 changes: 26 additions & 6 deletions src/main/java/me/marioogg/command/bungee/BungeeCommandHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,39 @@
import me.marioogg.command.Subcommand;
import me.marioogg.command.common.help.Help;
import me.marioogg.command.common.help.HelpNode;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.plugin.Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
* Handles command and processor registration for Bungee.
* Handles command and processor registration for BungeeCord.
*/
public class BungeeCommandHandler {

@Getter @Setter private static Plugin plugin;
@Getter private static Logger logger;

@Getter @Setter private static String noPermissionMessage = ChatColor.RED + "I'm sorry, but you do not have permission to perform this command.";
@Getter @Setter private static String playerOnlyMessage = ChatColor.RED + "You must be a player to execute this command.";
@Getter @Setter private static String consoleOnlyMessage = ChatColor.RED + "This command can only be executed by console.";
@Getter @Setter private static String internalErrorMessage = ChatColor.RED + "An internal error occurred while executing this command.";
@Getter @Setter private static String cooldownMessage = ChatColor.RED + "You must wait {seconds} more second(s) before using this command again.";
@Getter @Setter private static String minValidationMessage = ChatColor.RED + "The value must be at least {min}.";
@Getter @Setter private static String maxValidationMessage = ChatColor.RED + "The value must be at most {max}.";
@Getter @Setter private static String matchesValidationMessage = ChatColor.RED + "Invalid format.";

@Getter private static final Logger logger = LoggerFactory.getLogger(plugin.getDescription().getName());

public static void setPlugin(Plugin plugin) {
BungeeCommandHandler.plugin = plugin;
logger = LoggerFactory.getLogger(plugin.getDescription().getName());
}

@SneakyThrows
public static void registerCommands(String path, Plugin plugin) {
BungeeCommandHandler.setPlugin(plugin);
ClassPath.from(plugin.getClass().getClassLoader()).getAllClasses().stream()
.filter(info -> info.getPackageName().startsWith(path))
.map(ClassPath.ClassInfo::load)
Expand Down Expand Up @@ -59,7 +76,7 @@ public static void registerCommands(Object commandClass) {
Subcommand subcommand = commandClass.getClass().getAnnotation(Subcommand.class);

Arrays.stream(commandClass.getClass().getDeclaredMethods()).forEach(method -> {
me.marioogg.command.Command command = method.getAnnotation(me.marioogg.command.Command.class);
Command command = method.getAnnotation(Command.class);
if (command == null) return;

if (subcommand != null) {
Expand Down Expand Up @@ -94,8 +111,11 @@ public static void registerProcessors(String path, Plugin plugin) {
.filter(info -> info.getPackageName().startsWith(path))
.filter(info -> info.load().getSuperclass().equals(BungeeProcessor.class))
.forEach(info -> {
try { BungeeParamProcessor.createProcessor((BungeeProcessor<?>) info.load().getDeclaredConstructor().newInstance());
} catch (Exception e) { logger.error("Error registering command processors: ", e); }
try {
BungeeParamProcessor.createProcessor((BungeeProcessor<?>) info.load().getDeclaredConstructor().newInstance());
} catch (Exception e) {
logger.error("Error registering command processors: ", e);
}
});
}

Expand All @@ -120,4 +140,4 @@ private static Command buildDerivedCommand(Command original, String[] newNames)
public boolean hidden() { return original.hidden(); }
};
}
}
}
Loading
Loading