package ts3client import ( "fmt" "log" "strings" "go-ts/pkg/protocol" ) // ============================================================================= // Channel Methods // ============================================================================= // GetChannels returns all known channels func (c *Client) GetChannels() []*Channel { c.channelsMu.RLock() defer c.channelsMu.RUnlock() channels := make([]*Channel, 0, len(c.channels)) for _, ch := range c.channels { channels = append(channels, ch) } return channels } // GetChannel returns a channel by ID func (c *Client) GetChannel(id uint64) *Channel { c.channelsMu.RLock() defer c.channelsMu.RUnlock() return c.channels[id] } // GetChannelByName returns the first channel matching the given name (case-insensitive substring match) func (c *Client) GetChannelByName(name string) *Channel { c.channelsMu.RLock() defer c.channelsMu.RUnlock() // Debug: log all available channels log.Printf("[GetChannelByName] Searching for: %q, Available channels (%d):", name, len(c.channels)) for id, ch := range c.channels { log.Printf("[GetChannelByName] - [%d] %q", id, ch.Name) } // First try exact match for _, ch := range c.channels { if ch.Name == name { return ch } } // Then try case-insensitive contains nameLower := strings.ToLower(name) for _, ch := range c.channels { if strings.Contains(strings.ToLower(ch.Name), nameLower) { return ch } } return nil } // GetCurrentChannel returns the client's current channel func (c *Client) GetCurrentChannel() *Channel { if c.selfInfo == nil { return nil } return c.GetChannel(c.selfInfo.ChannelID) } // JoinChannel moves the client to the specified channel func (c *Client) JoinChannel(channelID uint64) error { return c.JoinChannelWithPassword(channelID, "") } // JoinChannelWithPassword moves the client to a password-protected channel func (c *Client) JoinChannelWithPassword(channelID uint64, password string) error { if c.internal == nil || c.selfInfo == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("clientmove") cmd.AddParam("clid", fmt.Sprintf("%d", c.selfInfo.ClientID)) cmd.AddParam("cid", fmt.Sprintf("%d", channelID)) cmd.AddParam("cpw", password) err := c.internal.SendCommand(cmd) if err == nil && c.selfInfo != nil { c.selfInfo.ChannelID = channelID } return err } // ============================================================================= // Message Methods // ============================================================================= // SendChannelMessage sends a message to the current channel func (c *Client) SendChannelMessage(message string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("sendtextmessage") cmd.AddParam("targetmode", "2") // Channel cmd.AddParam("msg", message) return c.internal.SendCommand(cmd) } // SendPrivateMessage sends a private message to a specific client func (c *Client) SendPrivateMessage(clientID uint16, message string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("sendtextmessage") cmd.AddParam("targetmode", "1") // Private cmd.AddParam("target", fmt.Sprintf("%d", clientID)) cmd.AddParam("msg", message) return c.internal.SendCommand(cmd) } // SendServerMessage sends a message to the entire server func (c *Client) SendServerMessage(message string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("sendtextmessage") cmd.AddParam("targetmode", "3") // Server cmd.AddParam("msg", message) return c.internal.SendCommand(cmd) } // ============================================================================= // Audio Methods // ============================================================================= // SendAudio sends PCM audio data to the server // PCM must be 48kHz, mono (960 samples for 20ms frame) func (c *Client) SendAudio(pcm []int16) error { if c.internal == nil { return fmt.Errorf("not connected") } return c.internal.SendVoice(pcm) } // SetInputMuted mutes or unmutes the microphone func (c *Client) SetInputMuted(muted bool) error { if c.internal == nil { return fmt.Errorf("not connected") } val := "0" if muted { val = "1" } cmd := protocol.NewCommand("clientupdate") cmd.AddParam("client_input_muted", val) return c.internal.SendCommand(cmd) } // SetOutputMuted mutes or unmutes the speaker func (c *Client) SetOutputMuted(muted bool) error { if c.internal == nil { return fmt.Errorf("not connected") } val := "0" if muted { val = "1" } cmd := protocol.NewCommand("clientupdate") cmd.AddParam("client_output_muted", val) return c.internal.SendCommand(cmd) } // ============================================================================= // Client Methods // ============================================================================= // GetClients returns all connected clients func (c *Client) GetClients() []*ClientInfo { c.clientsMu.RLock() defer c.clientsMu.RUnlock() clients := make([]*ClientInfo, 0, len(c.clients)) for _, cl := range c.clients { clients = append(clients, cl) } return clients } // GetClientByID returns a client by ID func (c *Client) GetClientByID(id uint16) *ClientInfo { c.clientsMu.RLock() defer c.clientsMu.RUnlock() return c.clients[id] } // KickFromChannel kicks a client from their current channel func (c *Client) KickFromChannel(clientID uint16, reason string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("clientkick") cmd.AddParam("clid", fmt.Sprintf("%d", clientID)) cmd.AddParam("reasonid", "4") // Kick from channel cmd.AddParam("reasonmsg", reason) return c.internal.SendCommand(cmd) } // KickFromServer kicks a client from the server func (c *Client) KickFromServer(clientID uint16, reason string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("clientkick") cmd.AddParam("clid", fmt.Sprintf("%d", clientID)) cmd.AddParam("reasonid", "5") // Kick from server cmd.AddParam("reasonmsg", reason) return c.internal.SendCommand(cmd) } // ============================================================================= // Info Methods // ============================================================================= // GetServerInfo returns server information func (c *Client) GetServerInfo() *ServerInfo { return c.serverInfo } // GetSelfInfo returns our own client information func (c *Client) GetSelfInfo() *SelfInfo { return c.selfInfo } // SetNickname changes the client's nickname func (c *Client) SetNickname(name string) error { if c.internal == nil { return fmt.Errorf("not connected") } cmd := protocol.NewCommand("clientupdate") cmd.AddParam("client_nickname", name) err := c.internal.SendCommand(cmd) if err == nil { c.config.Nickname = name if c.selfInfo != nil { c.selfInfo.Nickname = name } } return err }