Cmdr: A fully extensible and type safe command console for Roblox Developers

is it possible to make the CMDR usable only to specific people?

I’m having a very weird issue with Cmdr. I am making a viewstats command so that my Moderators can view other players stats by typing their username with the auto-complete menu if they’re in the server or use the @ prefix to type their full username if they’re not in the server. However, the issue is that when Cmdr gets to a point where it needs to return something inside the command, it just doesn’t. And that happens for every command for some really weird reason. After using commands a bunch more times, the very delayed command responses I should’ve gotten ages ago finally appear.

I tried using print statements and they got where they needed to. Once it gets to a return statement, it just self destructs.

Note: The game uses ProfileService and the game is called RoKarate. You can search it using the search bar and it’ll be the first game. I have confirmed that every variable gives what it’s supposed to such as the DataManager existing and the DataManager giving the current profile if they are in the server and all that.

Here is the following code I used:

return {
	Name = "viewstats",
	Aliases = {"stats"},
	Description = "View a players stats.",
	Group = "Moderator",
	Args = {
		{
			Type = "player @ string",
			Name = "player @ username",
			Description = "The full username of the player."
		}
	}
}
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataManager = require(ServerStorage.DataManager) -- This does exist
local Format = require(ReplicatedStorage.Modules.FormatNumber.Simple) -- This does exist

local function toDHMS(seconds)
	local days = math.floor(seconds / 86400)
	local hours = math.floor((seconds % 86400) / 3600)
	local minutes = math.floor((seconds % 3600) / 60)
	local seconds = math.floor(seconds % 60)
	return ("%i:%02i:%02i:%02i"):format(days,hours,minutes,seconds)
end

return function(context, player)
	if typeof(player) == "Instance" then
		local profile = DataManager:GetProfile(player) -- This does work
		
		context:Reply(player.Name.."'s stats:")
		context:Reply("---------------------------------------------")
		context:Reply("Belt: "..profile.Data.Belt)
		context:Reply("Strength: "..Format.Format(profile.Data.Strength))
		context:Reply("Health: "..Format.Format(profile.Data.Health))
		context:Reply("MaxStamina: "..Format.Format(profile.Data.MaxStamina))
		context:Reply("Wins: "..Format.Format(profile.Data.Wins))
		context:Reply("Kills: "..Format.Format(profile.Data.TotalKills))
		context:Reply("Robux Donated: "..Format.Format(profile.Data.RobuxDonated))
		context:Reply("Playtime: "..toDHMS(profile.Data.Playtime))
		context:Reply("---------------------------------------------")

		return "Successfully retrieved the players stats."
	elseif typeof(player) == "string" then
		local gotUserId, userId = pcall(function()
			return Players:GetUserIdFromNameAsync(player)
		end)

		if not gotUserId then
			return `Failed to get UserId associated with {player}.\nError: {userId}`
		end

		local profileStore = DataManager:GetProfileStore()
		local profile = profileStore:ViewProfileAsync("Player_"..userId)

		if profile == nil then
			return player.." does not have any data!"
		end

		context:Reply(player.."'s stats:")
		context:Reply("---------------------------------------------")
		context:Reply("Belt: "..profile.Data.Belt)
		context:Reply("Strength: "..Format.Format(profile.Data.Strength))
		context:Reply("Health: "..Format.Format(profile.Data.Health))
		context:Reply("MaxStamina: "..Format.Format(profile.Data.MaxStamina))
		context:Reply("Wins: "..Format.Format(profile.Data.Wins))
		context:Reply("Kills: "..Format.Format(profile.Data.TotalKills))
		context:Reply("Robux Donated: "..Format.Format(profile.Data.RobuxDonated))
		context:Reply("Playtime: "..toDHMS(profile.Data.Playtime))
		context:Reply("---------------------------------------------")
		
		return "Successfully retrieved the players stats."
	end
end
1 Like

I’m having an issue where the mobile keyboard constantly flickers on/off. Looks like it changes every frame. This starts happening when you tap the command panel.

All I’m doing is ‘:Toggle()’ once to open the gui.

Does someone know how to fix this?

this is incredible and i love it
however, i would like a feature where only a few people (like moderators, admins) can toggle the UI
cause right now any player can just press a given key to toggle the UI which is eh

If you have your own rank module, and you’re able to get the rank the player has on the client, you can use the SetEnabled method to completely hide the UI.

That’s what I do here:
image_2024-12-26_152614420

1 Like

How would I be able to make the ability to see logs of all the commands on cmdr?

And also how would I be able to make it so they can only provide 1 player arg at a time?

1 Like

i had no idea that existed
i appreciate you

How would you do that “rank” thing?

you can prob make a rank module then put the admins’ user id in the ranks
then check on the client if they have a rank and if not, it disables CMDR since it’s on the client
something like this

-- the module script
local Ranks = {}

Ranks.Owner = {game.CreatorId}
Ranks.Developer = {1}

-- checks if a player has a rank. returns true if true, false if not
function Ranks:HasRank(player: Player)
      if table.find(Ranks.Developer, player.UserId) then
             return true
      end
      return false
end

return Ranks

-- the local script
local Ranks = require(path to module script)
local player = game.Players.LocalPlayer
if Ranks:HasRank(player) == false then
       cmdr:SetEnabled(false)
end

I feel there is a more simple way to this, this seem excessive

CC @ltsmrsky

The way @officialnabalt wrote it is one way to go about it, but instead of seperate tables for each rank I would just put it all together in one table (Don’t mind the blurred bits):
image

After this, I would convert each rank into a number (Ex: Tester rank is 1, Admin is 2, etc)

The way I check the rank is a bit complicated, cause I’m not requiring the module on the client directly, but instead I’m using a remote function to get the rank on the server (To prevent spoofing, considering this is an admin system)

i suck at programming my bad man :sob:

Can you give me some code? Can’t really do much without any sample code

Client Script Example:

local Remote = game.ReplicatedStorage.RemoteFunction
local Rank = Remote:InvokeServer() -- Get the player's rank

if Rank <= 0 then
    Cmdr:SetEnabled(false)
end

Server Script Example:

local Remote = game.ReplicatedStorage.RemoteFunction
local Ranks = require(script.Ranks)

Remote.OnServerInvoke = function(Player)
    return Ranks:CheckRank(Player) -- Make a check rank function and return the result
end

Here’s an example. I am not giving out the code I use to check a player’s rank, but this should suffice.

1 Like

A rank system really isn’t that hard, I hate to say it but if you aren’t really experienced with luau I wouldn’t recommend using this module. This system doesn’t give you a lot of commands out of the box as its designed for you to make your own commands tailored towards your game.

The docs for this system are more than helpful and I’d highly recommend taking a look into them. Particularly this one - Registry | Cmdr

1 Like

Sorry it was 10pm for me and I regret making that post, but thanks for your contribution

Actually, do you know a way to be able to punish offline players? I need this for our muting system. I’ve checked the docs but I haven’t found anything; and I can’t use playerid because my muting system uses player names. Unless I forked it a lot which would take a while

Add a separate parameter to run the command using user ID by using the Cmdr data type for numbers or you could modify your code to write to a datastore for the next time the player joins by getting the players user ID by their username.

Moving this to DMs because this contains slightly private information

I’ve got this system to unban players, why won’t it work? The for players loop just won’t run.

Client:

return {
	Name = "unban";
	Description = "Unbans a player or a group of players";
	Group = "Moderator";
	Args = {
		{
			Type = "playerIds";
			Name = "players";
			Description = "The players to unban - Use player ids";
		},
	};
}

Server:

local Players = game:GetService("Players")

return function (context, players)
	for _, player in pairs(players) do
		Players:UnbanAsync(player.UserId)
	end

	return("Unbanned %d players."):format(#players)
end

Can anyone help me?