feat: Implement core voicebot functionality with TeamSpeak 3 and xAI integration.
This commit is contained in:
@@ -23,6 +23,31 @@ func ParseCommand(data []byte) (string, map[string]string) {
|
||||
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(
|
||||
|
||||
@@ -132,9 +132,11 @@ func (c *Client) Connect() error {
|
||||
err := c.internal.Connect(c.address)
|
||||
if err != nil {
|
||||
c.emit(EventDisconnected, &DisconnectedEvent{Reason: err.Error()})
|
||||
log.Printf("[TS3Client] Connect returning with error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[TS3Client] Connect returning cleanly")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -154,18 +156,24 @@ func (c *Client) ConnectAsync() <-chan error {
|
||||
|
||||
// Disconnect closes the connection gracefully
|
||||
func (c *Client) Disconnect() {
|
||||
log.Println("[Disconnect] Starting disconnect sequence...")
|
||||
if c.internal != nil {
|
||||
// Send disconnect command to server
|
||||
log.Println("[Disconnect] Sending disconnect command...")
|
||||
c.sendDisconnect("leaving")
|
||||
// Small delay to allow packet to be sent
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Wait for packet to be sent and ACKed - the internal loop must still be running
|
||||
log.Println("[Disconnect] Waiting for disconnect to be processed...")
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
// Stop the internal loop
|
||||
log.Println("[Disconnect] Stopping internal loop...")
|
||||
c.internal.Stop()
|
||||
if c.internal.Conn != nil {
|
||||
log.Println("[Disconnect] Closing connection...")
|
||||
c.internal.Conn.Close()
|
||||
}
|
||||
}
|
||||
c.connected = false
|
||||
log.Println("[Disconnect] Done")
|
||||
c.emit(EventDisconnected, &DisconnectedEvent{Reason: "client disconnect"})
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,35 @@ func (c *Client) SendAudio(pcm []int16) error {
|
||||
return c.sendJSON(msg)
|
||||
}
|
||||
|
||||
// SendText sends a text message to trigger a Grok response
|
||||
func (c *Client) SendText(text string) error {
|
||||
// Create conversation item with text
|
||||
createMsg := ConversationItemCreate{
|
||||
Type: "conversation.item.create",
|
||||
Item: ConversationItem{
|
||||
Type: "message",
|
||||
Role: "user",
|
||||
Content: []ItemContent{
|
||||
{Type: "input_text", Text: text},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := c.sendJSON(createMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Request response
|
||||
responseMsg := ResponseCreate{
|
||||
Type: "response.create",
|
||||
Response: ResponseSettings{
|
||||
Modalities: []string{"text", "audio"},
|
||||
},
|
||||
}
|
||||
|
||||
return c.sendJSON(responseMsg)
|
||||
}
|
||||
|
||||
// Close closes the WebSocket connection
|
||||
func (c *Client) Close() {
|
||||
c.mu.Lock()
|
||||
@@ -179,6 +208,13 @@ func (c *Client) receiveLoop() {
|
||||
|
||||
_, message, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
// Check if closed intentionally
|
||||
select {
|
||||
case <-c.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
||||
log.Println("[xAI] Connection closed normally")
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user