From b9ffb5f30d64d365e6792d0d3acff552d9fcd0fd Mon Sep 17 00:00:00 2001
From: Perttu Ahola <celeron55@gmail.com>
Date: Fri, 30 Mar 2012 01:45:23 +0300
Subject: [PATCH] minetest.register_chatcommand(cmd, def)

---
 builtin/builtin.lua   | 110 ++++++++++++++++++++++++++++++++++++++++++
 doc/lua_api.txt       |   9 ++++
 src/auth.cpp          |   6 +++
 src/servercommand.cpp |  21 +-------
 4 files changed, 126 insertions(+), 20 deletions(-)

diff --git a/builtin/builtin.lua b/builtin/builtin.lua
index 2be7a4dcf..71cd4cf53 100644
--- a/builtin/builtin.lua
+++ b/builtin/builtin.lua
@@ -821,6 +821,116 @@ function minetest.after(time, func, param)
 		table.insert(minetest.timers_to_add, {time=time, func=func, param=param})
 end
 
+function minetest.check_player_privs(name, privs)
+	local player_privs = minetest.get_player_privs(name)
+	local missing_privileges = {}
+	for priv, val in pairs(privs) do
+		if val then
+			if not player_privs[priv] then
+				table.insert(missing_privileges, priv)
+			end
+		end
+	end
+	if #missing_privileges > 0 then
+		return false, missing_privileges
+	end
+	return true, ""
+end
+
+--
+-- Chat commands
+--
+
+minetest.chatcommands = {}
+function minetest.register_chatcommand(cmd, def)
+	def = def or {}
+	def.params = def.params or ""
+	def.description = def.description or ""
+	def.privs = def.privs or {}
+	minetest.chatcommands[cmd] = def
+end
+
+-- Register the help command
+minetest.register_chatcommand("help", {
+	privs = {},
+	params = "(nothing)/all/<cmd>",
+	description = "Get help for commands",
+	func = function(name, param)
+		local format_help_line = function(cmd, def)
+			local msg = "/"..cmd
+			if def.params and def.params ~= "" then msg = msg .. " " .. def.params end
+			if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end
+			return msg
+		end
+		if not param or param == "" then
+			local msg = ""
+			cmds = {}
+			for cmd, def in pairs(minetest.chatcommands) do
+				if minetest.check_player_privs(name, def.privs) then
+					table.insert(cmds, cmd)
+				end
+			end
+			minetest.chat_send_player(name, "Available commands: "..table.concat(cmds, " "))
+			minetest.chat_send_player(name, "Use '/help <cmd>' to get more information, or '/help all' to list everything.")
+		elseif param == "all" then
+			minetest.chat_send_player(name, "Available commands:")
+			for cmd, def in pairs(minetest.chatcommands) do
+				if minetest.check_player_privs(name, def.privs) then
+					minetest.chat_send_player(name, format_help_line(cmd, def))
+				end
+			end
+		else
+			local cmd = param
+			def = minetest.chatcommands[cmd]
+			if not def then
+				minetest.chat_send_player(name, "Command not available: "..cmd)
+			else
+				minetest.chat_send_player(name, format_help_line(cmd, def))
+			end
+		end
+	end,
+})
+
+-- Register C++ commands without functions
+minetest.register_chatcommand("me", {params = nil, description = "chat action (eg. /me orders a pizza)"})
+minetest.register_chatcommand("status", {description = "print server status line"})
+minetest.register_chatcommand("privs", {params = "<name>", description = "print out privileges of player"})
+minetest.register_chatcommand("shutdown", {params = "", description = "shutdown server", privs = {server=true}})
+minetest.register_chatcommand("setting", {params = "<name> = <value>", description = "set line in configuration file", privs = {server=true}})
+minetest.register_chatcommand("clearobjects", {params = "", description = "clear all objects in world", privs = {server=true}})
+minetest.register_chatcommand("time", {params = "<0...24000>", description = "set time of day", privs = {settime=true}})
+minetest.register_chatcommand("teleport", {params = "<X>,<Y>,<Z>", description = "teleport to given position", privs = {teleport=true}})
+minetest.register_chatcommand("grant", {params = "<name> <privilege>", description = "Give privilege to player", privs = {privs=true}})
+minetest.register_chatcommand("revoke", {params = "<name> <privilege>", description = "Remove privilege from player", privs = {privs=true}})
+minetest.register_chatcommand("ban", {params = "<name>", description = "ban IP of player", privs = {ban=true}})
+minetest.register_chatcommand("unban", {params = "<name/ip>", description = "remove IP ban", privs = {ban=true}})
+minetest.register_chatcommand("setpassword", {params = "<name> <password>", description = "set given password", privs = {password=true}})
+minetest.register_chatcommand("clearpassword", {params = "<name>", description = "set empty password", privs = {password=true}})
+
+--
+-- Builtin chat handler
+--
+
+minetest.register_on_chat_message(function(name, message)
+	local cmd, param = string.match(message, "/([^ ]+) *(.*)")
+	local cmd_def = minetest.chatcommands[cmd]
+	if cmd_def then
+		if not cmd_def.func then
+			-- This is a C++ command
+			return false
+		else
+			local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs)
+			if has_privs then
+				cmd_def.func(name, param)
+			else
+				minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")")
+			end
+			return true -- handled chat message
+		end
+	end
+	return false
+end)
+
 --
 -- Set random seed
 --
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index caa5514b7..740f73b07 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -499,6 +499,7 @@ minetest.register_on_respawnplayer(func(ObjectRef))
 ^ return true in func to disable regular player placement
 ^ currently called _before_ repositioning of player occurs
 minetest.register_on_chat_message(func(name, message))
+minetest.register_chatcommand(cmd, chatcommand definition)
 
 minetest.add_to_creative_inventory(itemstring)
 minetest.setting_get(name) -> string or nil
@@ -507,6 +508,7 @@ minetest.setting_getbool(name) -> boolean value or nil
 minetest.chat_send_all(text)
 minetest.chat_send_player(name, text)
 minetest.get_player_privs(name) -> set of privs
+minetest.check_player_privs(name, {priv1=true,...}) -> bool, missing_privs
 minetest.get_inventory(location) -> InvRef
 ^ location = eg. {type="player", name="celeron55"}
                  {type="node", pos={x=, y=, z=}}
@@ -858,4 +860,11 @@ Recipe (furnace fuel):
     burntime = 1,
 }
 
+Chatcommand definition (register_chatcommand)
+{
+    params = "<name> <privilege>", -- short parameter description
+    description = "Remove privilege from player", -- full description
+    privs = {privs=true}}, -- require the "privs" privilege to run
+    func = function(name, param), -- called when command is run
+}
 
diff --git a/src/auth.cpp b/src/auth.cpp
index fce521e13..cafeb38d4 100644
--- a/src/auth.cpp
+++ b/src/auth.cpp
@@ -36,6 +36,8 @@ std::set<std::string> privsToSet(u64 privs)
 		s.insert("settime");
 	if(privs & PRIV_PRIVS)
 		s.insert("privs");
+	if(privs & PRIV_SERVER)
+		s.insert("server");
 	if(privs & PRIV_SHOUT)
 		s.insert("shout");
 	if(privs & PRIV_BAN)
@@ -60,6 +62,8 @@ std::string privsToString(u64 privs)
 		os<<"settime,";
 	if(privs & PRIV_PRIVS)
 		os<<"privs,";
+	if(privs & PRIV_SERVER)
+		os<<"server,";
 	if(privs & PRIV_SHOUT)
 		os<<"shout,";
 	if(privs & PRIV_BAN)
@@ -98,6 +102,8 @@ u64 stringToPrivs(std::string str)
 			privs |= PRIV_SETTIME;
 		else if(s == "privs")
 			privs |= PRIV_PRIVS;
+		else if(s == "server")
+			privs |= PRIV_SERVER;
 		else if(s == "shout")
 			privs |= PRIV_SHOUT;
 		else if(s == "ban")
diff --git a/src/servercommand.cpp b/src/servercommand.cpp
index c5b242b2e..48ada56fe 100644
--- a/src/servercommand.cpp
+++ b/src/servercommand.cpp
@@ -376,26 +376,7 @@ std::wstring processServerCommand(ServerCommandContext *ctx)
 	std::wostringstream os(std::ios_base::binary);
 	ctx->flags = SEND_TO_SENDER;	// Default, unless we change it.
 
-	u64 privs = ctx->privs;
-
-	if(ctx->parms.size() == 0 || ctx->parms[0] == L"help")
-	{
-		os<<L"-!- Available commands: ";
-		os<<L"me status privs";
-		if(privs & PRIV_SERVER)
-			os<<L" shutdown setting clearobjects";
-		if(privs & PRIV_SETTIME)
-			os<<L" time";
-		if(privs & PRIV_TELEPORT)
-			os<<L" teleport";
-		if(privs & PRIV_PRIVS)
-			os<<L" grant revoke";
-		if(privs & PRIV_BAN)
-			os<<L" ban unban";
-		if(privs & PRIV_PASSWORD)
-			os<<L" setpassword clearpassword";
-	}
-	else if(ctx->parms[0] == L"status")
+	if(ctx->parms[0] == L"status")
 		cmd_status(os, ctx);
 	else if(ctx->parms[0] == L"privs")
 		cmd_privs(os, ctx);
-- 
GitLab