Files
go-ts/pkg/ts3client/commands.go
Jose Luis Montañes Ojados 184fff202f Fix channel join flooding and enhance TUI features
- Implemented separate PacketID counters for Ping, Pong, and Ack (protocol compliance).
- Encrypted outgoing Pong packets after handshake.
- Fixed 'clientmove' command by omitting empty 'cpw' parameter.
- Added fullscreen log view toggle ('f' key).
- Improved logging with multi-writer and timestamps.
- Updated .gitignore to exclude binaries and logs.
2026-01-16 16:02:17 +01:00

270 lines
6.9 KiB
Go

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))
if password != "" {
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
}