feat: Implement core voicebot functionality with TeamSpeak 3 and xAI integration.
This commit is contained in:
@@ -154,264 +154,269 @@ func (c *Client) handleCommand(pkt *protocol.Packet) error {
|
||||
|
||||
log.Printf("Command: %s", cmdStr)
|
||||
|
||||
// Parse Command
|
||||
cmd, args := protocol.ParseCommand([]byte(cmdStr))
|
||||
// Parse Commands (possibly multiple piped items)
|
||||
commands := protocol.ParseCommands([]byte(cmdStr))
|
||||
|
||||
switch cmd {
|
||||
case "initivexpand2":
|
||||
err := c.Handshake.ProcessInitivexpand2(args)
|
||||
if err != nil {
|
||||
log.Printf("Error processing initivexpand2: %v", err)
|
||||
}
|
||||
case "initserver":
|
||||
// Server sends this after clientinit - contains our clientID
|
||||
if cid, ok := args["aclid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
c.ClientID = uint16(id)
|
||||
log.Printf("Assigned ClientID: %d", c.ClientID)
|
||||
}
|
||||
if name, ok := args["virtualserver_name"]; ok {
|
||||
c.ServerName = protocol.Unescape(name)
|
||||
log.Printf("Server Name: %s", c.ServerName)
|
||||
}
|
||||
c.emitEvent("connected", map[string]any{
|
||||
"clientID": c.ClientID,
|
||||
"serverName": c.ServerName,
|
||||
})
|
||||
case "channellist":
|
||||
// Parse channel info
|
||||
ch := &Channel{}
|
||||
if cid, ok := args["cid"]; ok {
|
||||
fmt.Sscanf(cid, "%d", &ch.ID)
|
||||
}
|
||||
if pid, ok := args["cpid"]; ok {
|
||||
fmt.Sscanf(pid, "%d", &ch.ParentID)
|
||||
}
|
||||
if name, ok := args["channel_name"]; ok {
|
||||
ch.Name = protocol.Unescape(name)
|
||||
}
|
||||
if order, ok := args["channel_order"]; ok {
|
||||
fmt.Sscanf(order, "%d", &ch.Order)
|
||||
}
|
||||
c.Channels[ch.ID] = ch
|
||||
log.Printf("Channel: [%d] NameRaw=%q Order=%d Args=%v", ch.ID, ch.Name, ch.Order, args)
|
||||
case "channellistfinished":
|
||||
log.Printf("=== Channel List Complete (%d channels) ===", len(c.Channels))
|
||||
var channelList []*Channel
|
||||
var targetChan *Channel
|
||||
for _, ch := range c.Channels {
|
||||
log.Printf(" - [%d] %s (parent=%d)", ch.ID, ch.Name, ch.ParentID)
|
||||
channelList = append(channelList, ch)
|
||||
if ch.Name == "Test" {
|
||||
targetChan = ch
|
||||
for _, command := range commands {
|
||||
cmd := command.Name
|
||||
args := command.Params
|
||||
|
||||
switch cmd {
|
||||
case "initivexpand2":
|
||||
err := c.Handshake.ProcessInitivexpand2(args)
|
||||
if err != nil {
|
||||
log.Printf("Error processing initivexpand2: %v", err)
|
||||
}
|
||||
}
|
||||
c.emitEvent("channel_list", map[string]any{
|
||||
"channels": channelList,
|
||||
})
|
||||
|
||||
if targetChan == nil {
|
||||
if ch, ok := c.Channels[2]; ok {
|
||||
log.Printf("Name parsing failed. Defaulting to Channel 2 as 'Test'.")
|
||||
targetChan = ch
|
||||
case "initserver":
|
||||
// Server sends this after clientinit - contains our clientID
|
||||
if cid, ok := args["aclid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
c.ClientID = uint16(id)
|
||||
log.Printf("Assigned ClientID: %d", c.ClientID)
|
||||
}
|
||||
}
|
||||
if name, ok := args["virtualserver_name"]; ok {
|
||||
c.ServerName = protocol.Unescape(name)
|
||||
log.Printf("Server Name: %s", c.ServerName)
|
||||
}
|
||||
c.emitEvent("connected", map[string]any{
|
||||
"clientID": c.ClientID,
|
||||
"serverName": c.ServerName,
|
||||
})
|
||||
case "channellist":
|
||||
// Parse channel info
|
||||
ch := &Channel{}
|
||||
if cid, ok := args["cid"]; ok {
|
||||
fmt.Sscanf(cid, "%d", &ch.ID)
|
||||
}
|
||||
if pid, ok := args["cpid"]; ok {
|
||||
fmt.Sscanf(pid, "%d", &ch.ParentID)
|
||||
}
|
||||
if name, ok := args["channel_name"]; ok {
|
||||
ch.Name = protocol.Unescape(name)
|
||||
}
|
||||
if order, ok := args["channel_order"]; ok {
|
||||
fmt.Sscanf(order, "%d", &ch.Order)
|
||||
}
|
||||
c.Channels[ch.ID] = ch
|
||||
log.Printf("Channel: [%d] NameRaw=%q Order=%d Args=%v", ch.ID, ch.Name, ch.Order, args)
|
||||
case "channellistfinished":
|
||||
log.Printf("=== Channel List Complete (%d channels) ===", len(c.Channels))
|
||||
var channelList []*Channel
|
||||
var targetChan *Channel
|
||||
for _, ch := range c.Channels {
|
||||
log.Printf(" - [%d] %s (parent=%d)", ch.ID, ch.Name, ch.ParentID)
|
||||
channelList = append(channelList, ch)
|
||||
if ch.Name == "Test" {
|
||||
targetChan = ch
|
||||
}
|
||||
}
|
||||
c.emitEvent("channel_list", map[string]any{
|
||||
"channels": channelList,
|
||||
})
|
||||
|
||||
if targetChan != nil {
|
||||
log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID)
|
||||
|
||||
if c.ClientID == 0 {
|
||||
log.Println("ERROR: ClientID is 0. Cannot join channel. 'initserver' missing?")
|
||||
return nil
|
||||
if targetChan == nil {
|
||||
if ch, ok := c.Channels[2]; ok {
|
||||
log.Printf("Name parsing failed. Defaulting to Channel 2 as 'Test'.")
|
||||
targetChan = ch
|
||||
}
|
||||
}
|
||||
|
||||
moveCmd := protocol.NewCommand("clientmove")
|
||||
moveCmd.AddParam("clid", fmt.Sprintf("%d", c.ClientID))
|
||||
moveCmd.AddParam("cid", fmt.Sprintf("%d", targetChan.ID))
|
||||
moveCmd.AddParam("cpw", "")
|
||||
if targetChan != nil {
|
||||
log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID)
|
||||
|
||||
return c.SendCommand(moveCmd)
|
||||
}
|
||||
case "notifycliententerview":
|
||||
// A client entered the server
|
||||
nick := ""
|
||||
clientID := uint16(0)
|
||||
channelID := uint64(0)
|
||||
if n, ok := args["client_nickname"]; ok {
|
||||
nick = protocol.Unescape(n)
|
||||
}
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if ctid, ok := args["ctid"]; ok {
|
||||
fmt.Sscanf(ctid, "%d", &channelID)
|
||||
}
|
||||
log.Printf("Client entered: %s (ID=%d)", nick, clientID)
|
||||
c.emitEvent("client_enter", map[string]any{
|
||||
"clientID": clientID,
|
||||
"nickname": nick,
|
||||
"channelID": channelID,
|
||||
})
|
||||
case "notifytextmessage":
|
||||
// targetmode: 1=Private, 2=Channel, 3=Server
|
||||
msg := ""
|
||||
invoker := "Unknown"
|
||||
var invokerID uint16
|
||||
var targetModeInt int
|
||||
if m, ok := args["msg"]; ok {
|
||||
msg = protocol.Unescape(m)
|
||||
}
|
||||
if name, ok := args["invokername"]; ok {
|
||||
invoker = protocol.Unescape(name)
|
||||
}
|
||||
if iid, ok := args["invokerid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(iid, "%d", &id)
|
||||
invokerID = uint16(id)
|
||||
}
|
||||
if c.ClientID == 0 {
|
||||
log.Println("ERROR: ClientID is 0. Cannot join channel. 'initserver' missing?")
|
||||
return nil
|
||||
}
|
||||
|
||||
targetMode := "Unknown"
|
||||
if tm, ok := args["targetmode"]; ok {
|
||||
switch tm {
|
||||
case "1":
|
||||
targetMode = "Private"
|
||||
targetModeInt = 1
|
||||
case "2":
|
||||
targetMode = "Channel"
|
||||
targetModeInt = 2
|
||||
case "3":
|
||||
targetMode = "Server"
|
||||
targetModeInt = 3
|
||||
moveCmd := protocol.NewCommand("clientmove")
|
||||
moveCmd.AddParam("clid", fmt.Sprintf("%d", c.ClientID))
|
||||
moveCmd.AddParam("cid", fmt.Sprintf("%d", targetChan.ID))
|
||||
moveCmd.AddParam("cpw", "")
|
||||
|
||||
return c.SendCommand(moveCmd)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[Chat][%s] %s: %s", targetMode, invoker, msg)
|
||||
c.emitEvent("message", map[string]any{
|
||||
"senderID": invokerID,
|
||||
"senderName": invoker,
|
||||
"message": msg,
|
||||
"targetMode": targetModeInt,
|
||||
})
|
||||
|
||||
case "notifyclientchatcomposing":
|
||||
// Someone is typing
|
||||
// We only get clid, need to map to name if possible, or just log clid
|
||||
clid := "Unknown"
|
||||
if id, ok := args["clid"]; ok {
|
||||
clid = id
|
||||
}
|
||||
log.Printf("Client %s is typing...", clid)
|
||||
|
||||
case "notifyclientmoved":
|
||||
// Client moved to another channel
|
||||
var clientID uint16
|
||||
var channelID uint64
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if ctid, ok := args["ctid"]; ok {
|
||||
fmt.Sscanf(ctid, "%d", &channelID)
|
||||
}
|
||||
log.Printf("Client %d moved to Channel %d", clientID, channelID)
|
||||
c.emitEvent("client_moved", map[string]any{
|
||||
"clientID": clientID,
|
||||
"channelID": channelID,
|
||||
})
|
||||
|
||||
case "notifyclientchannelgroupchanged":
|
||||
// Client channel group changed
|
||||
// invokerid=0 invokername=Server cgid=8 cid=1 clid=3 cgi=1
|
||||
invoker := "Unknown"
|
||||
if name, ok := args["invokername"]; ok {
|
||||
invoker = protocol.Unescape(name)
|
||||
}
|
||||
log.Printf("Client %s channel group changed to %s in Channel %s by %s",
|
||||
args["clid"], args["cgid"], args["cid"], invoker)
|
||||
|
||||
case "notifyconnectioninforequest":
|
||||
// Server asking for connection info. We MUST reply to update Ping in UI and avoid timeout.
|
||||
log.Println("Server requested connection info. sending 'setconnectioninfo'...")
|
||||
|
||||
cmd := protocol.NewCommand("setconnectioninfo")
|
||||
cmd.AddParam("connection_ping", "50")
|
||||
cmd.AddParam("connection_ping_deviation", "5")
|
||||
|
||||
// Detailed stats for each kind as seen in ts3j (KEEPALIVE, SPEECH, CONTROL)
|
||||
kinds := []string{"keepalive", "speech", "control"}
|
||||
for _, k := range kinds {
|
||||
cmd.AddParam("connection_packets_sent_"+k, "500")
|
||||
cmd.AddParam("connection_packets_received_"+k, "500")
|
||||
cmd.AddParam("connection_bytes_sent_"+k, "25000")
|
||||
cmd.AddParam("connection_bytes_received_"+k, "25000")
|
||||
cmd.AddParam("connection_bandwidth_sent_last_second_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_received_last_second_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_sent_last_minute_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_received_last_minute_"+k, "200")
|
||||
cmd.AddParam("connection_server2client_packetloss_"+k, "0")
|
||||
}
|
||||
cmd.AddParam("connection_server2client_packetloss_total", "0")
|
||||
|
||||
return c.SendCommand(cmd)
|
||||
|
||||
case "notifyclientleftview":
|
||||
// Client left the server
|
||||
var clientID uint16
|
||||
reason := ""
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if rid, ok := args["reasonid"]; ok {
|
||||
switch rid {
|
||||
case "3":
|
||||
reason = "connection lost"
|
||||
case "5":
|
||||
reason = "kicked"
|
||||
case "6":
|
||||
reason = "banned"
|
||||
case "8":
|
||||
reason = "leaving"
|
||||
default:
|
||||
reason = "unknown"
|
||||
case "notifycliententerview":
|
||||
// A client entered the server
|
||||
nick := ""
|
||||
clientID := uint16(0)
|
||||
channelID := uint64(0)
|
||||
if n, ok := args["client_nickname"]; ok {
|
||||
nick = protocol.Unescape(n)
|
||||
}
|
||||
}
|
||||
if rmsg, ok := args["reasonmsg"]; ok {
|
||||
if rmsg != "" {
|
||||
reason = protocol.Unescape(rmsg)
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if ctid, ok := args["ctid"]; ok {
|
||||
fmt.Sscanf(ctid, "%d", &channelID)
|
||||
}
|
||||
log.Printf("Client entered: %s (ID=%d)", nick, clientID)
|
||||
c.emitEvent("client_enter", map[string]any{
|
||||
"clientID": clientID,
|
||||
"nickname": nick,
|
||||
"channelID": channelID,
|
||||
})
|
||||
case "notifytextmessage":
|
||||
// targetmode: 1=Private, 2=Channel, 3=Server
|
||||
msg := ""
|
||||
invoker := "Unknown"
|
||||
var invokerID uint16
|
||||
var targetModeInt int
|
||||
if m, ok := args["msg"]; ok {
|
||||
msg = protocol.Unescape(m)
|
||||
}
|
||||
if name, ok := args["invokername"]; ok {
|
||||
invoker = protocol.Unescape(name)
|
||||
}
|
||||
if iid, ok := args["invokerid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(iid, "%d", &id)
|
||||
invokerID = uint16(id)
|
||||
}
|
||||
|
||||
targetMode := "Unknown"
|
||||
if tm, ok := args["targetmode"]; ok {
|
||||
switch tm {
|
||||
case "1":
|
||||
targetMode = "Private"
|
||||
targetModeInt = 1
|
||||
case "2":
|
||||
targetMode = "Channel"
|
||||
targetModeInt = 2
|
||||
case "3":
|
||||
targetMode = "Server"
|
||||
targetModeInt = 3
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[Chat][%s] %s: %s", targetMode, invoker, msg)
|
||||
c.emitEvent("message", map[string]any{
|
||||
"senderID": invokerID,
|
||||
"senderName": invoker,
|
||||
"message": msg,
|
||||
"targetMode": targetModeInt,
|
||||
})
|
||||
|
||||
case "notifyclientchatcomposing":
|
||||
// Someone is typing
|
||||
// We only get clid, need to map to name if possible, or just log clid
|
||||
clid := "Unknown"
|
||||
if id, ok := args["clid"]; ok {
|
||||
clid = id
|
||||
}
|
||||
log.Printf("Client %s is typing...", clid)
|
||||
|
||||
case "notifyclientmoved":
|
||||
// Client moved to another channel
|
||||
var clientID uint16
|
||||
var channelID uint64
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if ctid, ok := args["ctid"]; ok {
|
||||
fmt.Sscanf(ctid, "%d", &channelID)
|
||||
}
|
||||
log.Printf("Client %d moved to Channel %d", clientID, channelID)
|
||||
c.emitEvent("client_moved", map[string]any{
|
||||
"clientID": clientID,
|
||||
"channelID": channelID,
|
||||
})
|
||||
|
||||
case "notifyclientchannelgroupchanged":
|
||||
// Client channel group changed
|
||||
// invokerid=0 invokername=Server cgid=8 cid=1 clid=3 cgi=1
|
||||
invoker := "Unknown"
|
||||
if name, ok := args["invokername"]; ok {
|
||||
invoker = protocol.Unescape(name)
|
||||
}
|
||||
log.Printf("Client %s channel group changed to %s in Channel %s by %s",
|
||||
args["clid"], args["cgid"], args["cid"], invoker)
|
||||
|
||||
case "notifyconnectioninforequest":
|
||||
// Server asking for connection info. We MUST reply to update Ping in UI and avoid timeout.
|
||||
log.Println("Server requested connection info. sending 'setconnectioninfo'...")
|
||||
|
||||
cmd := protocol.NewCommand("setconnectioninfo")
|
||||
cmd.AddParam("connection_ping", "50")
|
||||
cmd.AddParam("connection_ping_deviation", "5")
|
||||
|
||||
// Detailed stats for each kind as seen in ts3j (KEEPALIVE, SPEECH, CONTROL)
|
||||
kinds := []string{"keepalive", "speech", "control"}
|
||||
for _, k := range kinds {
|
||||
cmd.AddParam("connection_packets_sent_"+k, "500")
|
||||
cmd.AddParam("connection_packets_received_"+k, "500")
|
||||
cmd.AddParam("connection_bytes_sent_"+k, "25000")
|
||||
cmd.AddParam("connection_bytes_received_"+k, "25000")
|
||||
cmd.AddParam("connection_bandwidth_sent_last_second_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_received_last_second_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_sent_last_minute_"+k, "200")
|
||||
cmd.AddParam("connection_bandwidth_received_last_minute_"+k, "200")
|
||||
cmd.AddParam("connection_server2client_packetloss_"+k, "0")
|
||||
}
|
||||
cmd.AddParam("connection_server2client_packetloss_total", "0")
|
||||
|
||||
return c.SendCommand(cmd)
|
||||
|
||||
case "notifyclientleftview":
|
||||
// Client left the server
|
||||
var clientID uint16
|
||||
reason := ""
|
||||
if cid, ok := args["clid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
clientID = uint16(id)
|
||||
}
|
||||
if rid, ok := args["reasonid"]; ok {
|
||||
switch rid {
|
||||
case "3":
|
||||
reason = "connection lost"
|
||||
case "5":
|
||||
reason = "kicked"
|
||||
case "6":
|
||||
reason = "banned"
|
||||
case "8":
|
||||
reason = "leaving"
|
||||
default:
|
||||
reason = "unknown"
|
||||
}
|
||||
}
|
||||
if rmsg, ok := args["reasonmsg"]; ok {
|
||||
if rmsg != "" {
|
||||
reason = protocol.Unescape(rmsg)
|
||||
}
|
||||
}
|
||||
log.Printf("Client %d left: %s", clientID, reason)
|
||||
c.emitEvent("client_left", map[string]any{
|
||||
"clientID": clientID,
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
case "notifyclientupdated":
|
||||
// Client updated (e.g. muted/unmuted)
|
||||
clid := args["clid"]
|
||||
log.Printf("Client %s updated: %v", clid, args)
|
||||
|
||||
case "error":
|
||||
// Server reported an error
|
||||
id := args["id"]
|
||||
msg := protocol.Unescape(args["msg"])
|
||||
log.Printf("SERVER ERROR: ID=%s MSG=%s", id, msg)
|
||||
c.emitEvent("error", map[string]any{
|
||||
"id": id,
|
||||
"message": msg,
|
||||
})
|
||||
|
||||
case "notifyservergrouplist", "notifychannelgrouplist", "notifyclientneededpermissions":
|
||||
// Ignore verbose noisy setup commands
|
||||
default:
|
||||
log.Printf("Unhandled command: %s Args: %v", cmd, args)
|
||||
}
|
||||
log.Printf("Client %d left: %s", clientID, reason)
|
||||
c.emitEvent("client_left", map[string]any{
|
||||
"clientID": clientID,
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
case "notifyclientupdated":
|
||||
// Client updated (e.g. muted/unmuted)
|
||||
clid := args["clid"]
|
||||
log.Printf("Client %s updated: %v", clid, args)
|
||||
|
||||
case "error":
|
||||
// Server reported an error
|
||||
id := args["id"]
|
||||
msg := protocol.Unescape(args["msg"])
|
||||
log.Printf("SERVER ERROR: ID=%s MSG=%s", id, msg)
|
||||
c.emitEvent("error", map[string]any{
|
||||
"id": id,
|
||||
"message": msg,
|
||||
})
|
||||
|
||||
case "notifyservergrouplist", "notifychannelgrouplist", "notifyclientneededpermissions":
|
||||
// Ignore verbose noisy setup commands
|
||||
default:
|
||||
log.Printf("Unhandled command: %s Args: %v", cmd, args)
|
||||
}
|
||||
} // End for loop
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user