summaryrefslogtreecommitdiff
path: root/server/commands.go
diff options
context:
space:
mode:
authorraven <citrons@mondecitronne.com>2026-03-21 00:31:49 -0500
committerraven <citrons@mondecitronne.com>2026-03-21 00:31:49 -0500
commit3f6a61d993d6c50135c3cefe3cf362390c4027d5 (patch)
tree8232e80a845480e01a3769269e1d67156085ecba /server/commands.go
parent22214f3fea9b2e201a9010ff1bf27bf52aeaf338 (diff)
chat commands
Diffstat (limited to 'server/commands.go')
-rw-r--r--server/commands.go222
1 files changed, 222 insertions, 0 deletions
diff --git a/server/commands.go b/server/commands.go
index abb4e43..0b56aaf 100644
--- a/server/commands.go
+++ b/server/commands.go
@@ -1 +1,223 @@
package server
+
+import (
+ "sort"
+ "strings"
+)
+
+type CommandSender interface {
+ OnCommandOutput(from *Server, output string)
+ OnCommandError(from *Server, err string)
+}
+type commandCtx struct {
+ server *Server
+ sender CommandSender
+ arg arguments
+ auth authLevel
+}
+type commandHandler func(commandCtx) (err string)
+
+func isCommand(message string) (isCmd bool, text string) {
+ text = message
+ if message[0] == '/' {
+ text = message[1:]
+ // an additional slash escapes the second //not command -> /not command
+ isCmd = len(text) != 0 && text[0] != '/'
+ }
+ return
+}
+
+func executeCommand(
+ s *Server, auth authLevel, from CommandSender, command string) {
+ ctx := commandCtx {
+ server: s,
+ sender: from,
+ arg: parseArgs(command),
+ auth: auth,
+ }
+ cmd, _ := ctx.arg.nextArg()
+ handler := commands[cmd]
+ var err string
+ if commandAuth[cmd] > auth {
+ err = "Permission denied"
+ } else if handler == nil {
+ err = "Unknown command: /" + cmd
+ } else {
+ err = handler(ctx)
+ }
+ if err != "" {
+ from.OnCommandError(s, err)
+ }
+}
+
+func usage(cmd string) string {
+ if help[cmd] != nil {
+ return "Usage: " + help[cmd][0]
+ } else {
+ return "Invalid usage"
+ }
+}
+
+var commands = map[string]commandHandler {
+ "op": func(ctx commandCtx) string {
+ name, ok := ctx.arg.nextArg()
+ if !ok {
+ return usage("op")
+ }
+ if !playerNameRegex.Match([]byte(name)) {
+ return "Unknown player."
+ }
+ ctx.server.changePlayerAuth(name, opAuth, func(ok bool) {
+ if ok {
+ ctx.sender.OnCommandOutput(ctx.server, "Opped.")
+ } else {
+ ctx.sender.OnCommandError(ctx.server, "Unknown player")
+ }
+ })
+ return ""
+ },
+ "deop": func(ctx commandCtx) string {
+ name, ok := ctx.arg.nextArg()
+ if !ok {
+ return usage("op")
+ }
+ ctx.server.changePlayerAuth(name, defaultAuth, func(ok bool) {
+ if ok {
+ ctx.sender.OnCommandOutput(ctx.server, "Deopped.")
+ } else {
+ ctx.sender.OnCommandError(ctx.server, "Unknown player")
+ }
+ })
+ return ""
+ },
+ "stop": func(ctx commandCtx) string {
+ ctx.server.Stop(nil)
+ return ""
+ },
+ "save": func(ctx commandCtx) string {
+ ctx.server.Save(nil)
+ return ""
+ },
+ "help": func(ctx commandCtx) string {
+ cmd, ok := ctx.arg.nextArg()
+ if !ok {
+ var usages []string
+ for cmd, helpMessages := range help {
+ if commandAuth[cmd] <= ctx.auth {
+ usages = append(usages, helpMessages[0])
+ }
+ }
+ sort.Strings(usages)
+ ctx.sender.OnCommandOutput(ctx.server, "Available commands:")
+ for _, usage := range usages {
+ ctx.sender.OnCommandOutput(ctx.server, "&7* &e" + usage)
+ }
+ return ""
+ }
+ if help[cmd] == nil {
+ return "Unknown command: /" + cmd
+ }
+ for n, line := range help[cmd] {
+ if n == 0 {
+ line = "Usage: " + line
+ } else {
+ line = " " + line
+ }
+ ctx.sender.OnCommandOutput(ctx.server, line)
+ }
+ return ""
+ },
+}
+
+var commandAuth = map[string]authLevel {
+ "op": opAuth,
+ "deop": opAuth,
+ "save": opAuth,
+ "stop": opAuth,
+}
+
+var help = map[string][]string {
+ "op": []string {
+ "/op <player>",
+ "Grant operator status",
+ },
+ "deop": []string {
+ "/deop <player>",
+ "Revoke operator status",
+ },
+ "save": []string {
+ "/save",
+ "Save all levels",
+ },
+ "stop": []string {
+ "/stop",
+ "Stop the server",
+ },
+ "help": []string {
+ "/help [command]",
+ },
+}
+
+type arguments struct {
+ rd *strings.Reader
+}
+
+func parseArgs(command string) arguments {
+ return arguments {strings.NewReader(command)}
+}
+
+func (arg *arguments) nextArg() (string, bool) {
+ var out strings.Builder
+ for {
+ b, err := arg.rd.ReadByte()
+ if err != nil {
+ return "", false
+ }
+ if b != ' ' {
+ arg.rd.UnreadByte()
+ break
+ }
+ }
+ var splitChar byte = ' '
+ b, err := arg.rd.ReadByte()
+ if err != nil {
+ return "", false
+ }
+ if b == '"' {
+ splitChar = '"'
+ } else {
+ arg.rd.UnreadByte()
+ }
+ for {
+ b, err = arg.rd.ReadByte()
+ if err != nil {
+ if out.Len() > 0 {
+ break
+ }
+ return "", false
+ }
+ if b == splitChar {
+ break
+ }
+ if b == '\\' {
+ b, err = arg.rd.ReadByte()
+ if err != nil {
+ b = '\\'
+ }
+ }
+ out.WriteByte(b)
+ }
+ return out.String(), true
+}
+
+func (arg *arguments) allArgs() []string {
+ var (a string; ok bool; args []string)
+ for {
+ a, ok = arg.nextArg()
+ if !ok {
+ break
+ }
+ args = append(args, a)
+ }
+ return args
+}