package protocol import ( "strings" ) // Command Parsing Helpers func ParseCommand(data []byte) (string, map[string]string) { s := string(data) parts := strings.Split(s, " ") cmd := parts[0] args := make(map[string]string) for _, p := range parts[1:] { kv := strings.SplitN(p, "=", 2) if len(kv) == 2 { val := Unescape(kv[1]) args[kv[0]] = val } else { args[p] = "" } } return cmd, args } // ParseCommands parses response that may contain multiple items separated by pipe (|) func ParseCommands(data []byte) []*Command { s := string(data) // TS3 uses pipe | to separate list items items := strings.Split(s, "|") cmds := make([]*Command, 0, len(items)) // First item contains the command name name, args := ParseCommand([]byte(items[0])) cmds = append(cmds, &Command{Name: name, Params: args}) // Subsequent items reuse the same command name for _, item := range items[1:] { // Hack: Prepend command name to reuse ParseCommand logic // or better: manually parse args. // Since ParseCommand splits by space, we can just use "DUMMY " + item // ensuring we trim properly. _, itemArgs := ParseCommand([]byte("CMD " + strings.TrimSpace(item))) cmds = append(cmds, &Command{Name: name, Params: itemArgs}) } return cmds } // Unescape TS3 string func Unescape(s string) string { r := strings.NewReplacer( `\/`, `/`, `\s`, ` `, `\p`, `|`, `\a`, "\a", `\b`, "\b", `\f`, "\f", `\n`, "\n", `\r`, "\r", `\t`, "\t", `\v`, "\v", `\\`, `\`, ) return r.Replace(s) } func Escape(s string) string { r := strings.NewReplacer( `\`, `\\`, `/`, `\/`, ` `, `\s`, `|`, `\p`, "\a", `\a`, "\b", `\b`, "\f", `\f`, "\n", `\n`, "\r", `\r`, "\t", `\t`, "\v", `\v`, ) return r.Replace(s) } // Command represents a TeamSpeak 3 command for building/encoding type Command struct { Name string Params map[string]string } func NewCommand(name string) *Command { return &Command{ Name: name, Params: make(map[string]string), } } func (c *Command) AddParam(key, value string) { c.Params[key] = value } func (c *Command) Encode() string { var sb strings.Builder sb.WriteString(c.Name) for k, v := range c.Params { sb.WriteString(" ") sb.WriteString(k) if v != "" { sb.WriteString("=") sb.WriteString(Escape(v)) } } return sb.String() }