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 ", "Grant operator status", }, "deop": []string { "/deop ", "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 }