feat: refactor client into reusable ts3client library
This commit is contained in:
368
pkg/ts3client/client.go
Normal file
368
pkg/ts3client/client.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package ts3client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go-ts/internal/client"
|
||||
)
|
||||
|
||||
// Client is the main TeamSpeak 3 client
|
||||
type Client struct {
|
||||
address string
|
||||
config Config
|
||||
|
||||
// Internal client
|
||||
internal *client.Client
|
||||
|
||||
// Event handlers
|
||||
handlers map[EventType][]any
|
||||
mu sync.RWMutex
|
||||
|
||||
// State
|
||||
connected bool
|
||||
channels map[uint64]*Channel
|
||||
clients map[uint16]*ClientInfo
|
||||
serverInfo *ServerInfo
|
||||
selfInfo *SelfInfo
|
||||
channelsMu sync.RWMutex
|
||||
clientsMu sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new TeamSpeak client
|
||||
func New(address string, config Config) *Client {
|
||||
// Apply defaults
|
||||
if config.Nickname == "" {
|
||||
config.Nickname = "GoTS3Bot"
|
||||
}
|
||||
if config.SecurityLevel == 0 {
|
||||
config.SecurityLevel = 8
|
||||
}
|
||||
if config.Version == "" {
|
||||
config.Version = "3.6.2 [Build: 1690976575]"
|
||||
}
|
||||
if config.Platform == "" {
|
||||
config.Platform = "Windows"
|
||||
}
|
||||
if config.HWID == "" {
|
||||
config.HWID = "1234567890"
|
||||
}
|
||||
|
||||
return &Client{
|
||||
address: address,
|
||||
config: config,
|
||||
handlers: make(map[EventType][]any),
|
||||
channels: make(map[uint64]*Channel),
|
||||
clients: make(map[uint16]*ClientInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// On registers an event handler
|
||||
// The handler function signature must match the event type:
|
||||
// - EventConnected: func(*ConnectedEvent)
|
||||
// - EventMessage: func(*MessageEvent)
|
||||
// - EventAudio: func(*AudioEvent)
|
||||
// - etc.
|
||||
func (c *Client) On(event EventType, handler any) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.handlers[event] = append(c.handlers[event], handler)
|
||||
}
|
||||
|
||||
// emit calls all handlers registered for the given event
|
||||
func (c *Client) emit(event EventType, data any) {
|
||||
c.mu.RLock()
|
||||
handlers := c.handlers[event]
|
||||
c.mu.RUnlock()
|
||||
|
||||
for _, h := range handlers {
|
||||
switch event {
|
||||
case EventConnected:
|
||||
if fn, ok := h.(func(*ConnectedEvent)); ok {
|
||||
fn(data.(*ConnectedEvent))
|
||||
}
|
||||
case EventDisconnected:
|
||||
if fn, ok := h.(func(*DisconnectedEvent)); ok {
|
||||
fn(data.(*DisconnectedEvent))
|
||||
}
|
||||
case EventMessage:
|
||||
if fn, ok := h.(func(*MessageEvent)); ok {
|
||||
fn(data.(*MessageEvent))
|
||||
}
|
||||
case EventClientEnter:
|
||||
if fn, ok := h.(func(*ClientEnterEvent)); ok {
|
||||
fn(data.(*ClientEnterEvent))
|
||||
}
|
||||
case EventClientLeft:
|
||||
if fn, ok := h.(func(*ClientLeftEvent)); ok {
|
||||
fn(data.(*ClientLeftEvent))
|
||||
}
|
||||
case EventClientMoved:
|
||||
if fn, ok := h.(func(*ClientMovedEvent)); ok {
|
||||
fn(data.(*ClientMovedEvent))
|
||||
}
|
||||
case EventChannelList:
|
||||
if fn, ok := h.(func(*ChannelListEvent)); ok {
|
||||
fn(data.(*ChannelListEvent))
|
||||
}
|
||||
case EventAudio:
|
||||
if fn, ok := h.(func(*AudioEvent)); ok {
|
||||
fn(data.(*AudioEvent))
|
||||
}
|
||||
case EventError:
|
||||
if fn, ok := h.(func(*ErrorEvent)); ok {
|
||||
fn(data.(*ErrorEvent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect establishes a connection to the TeamSpeak server
|
||||
// This method blocks until disconnected or an error occurs
|
||||
func (c *Client) Connect() error {
|
||||
c.internal = client.NewClient(c.config.Nickname)
|
||||
|
||||
// Set event callback on internal client
|
||||
c.internal.SetEventHandler(c.handleInternalEvent)
|
||||
|
||||
log.Printf("Connecting to %s as %s...", c.address, c.config.Nickname)
|
||||
|
||||
err := c.internal.Connect(c.address)
|
||||
if err != nil {
|
||||
c.emit(EventDisconnected, &DisconnectedEvent{Reason: err.Error()})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectAsync connects in the background and returns immediately
|
||||
func (c *Client) ConnectAsync() <-chan error {
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
if err := c.Connect(); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
close(errChan)
|
||||
}()
|
||||
// Give it a moment to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return errChan
|
||||
}
|
||||
|
||||
// Disconnect closes the connection gracefully
|
||||
func (c *Client) Disconnect() {
|
||||
if c.internal != nil {
|
||||
// Send disconnect command to server
|
||||
c.sendDisconnect("leaving")
|
||||
// Small delay to allow packet to be sent
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Stop the internal loop
|
||||
c.internal.Stop()
|
||||
if c.internal.Conn != nil {
|
||||
c.internal.Conn.Close()
|
||||
}
|
||||
}
|
||||
c.connected = false
|
||||
c.emit(EventDisconnected, &DisconnectedEvent{Reason: "client disconnect"})
|
||||
}
|
||||
|
||||
// sendDisconnect sends the disconnect command to the server
|
||||
func (c *Client) sendDisconnect(reason string) {
|
||||
if c.internal == nil {
|
||||
return
|
||||
}
|
||||
// Use internal client's SendCommand
|
||||
cmd := "clientdisconnect reasonid=8 reasonmsg=" + escapeTS3(reason)
|
||||
log.Printf("Sending disconnect: %s", cmd)
|
||||
if err := c.internal.SendCommandString(cmd); err != nil {
|
||||
log.Printf("Error sending disconnect: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type disconnectCommand struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
func (d *disconnectCommand) encode() string {
|
||||
return "clientdisconnect reasonid=8 reasonmsg=" + escapeTS3(d.reason)
|
||||
}
|
||||
|
||||
func escapeTS3(s string) string {
|
||||
// Basic escape for TS3 protocol
|
||||
result := ""
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\\':
|
||||
result += "\\\\"
|
||||
case '/':
|
||||
result += "\\/"
|
||||
case ' ':
|
||||
result += "\\s"
|
||||
case '|':
|
||||
result += "\\p"
|
||||
default:
|
||||
result += string(r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsConnected returns true if the client is connected
|
||||
func (c *Client) IsConnected() bool {
|
||||
return c.connected
|
||||
}
|
||||
|
||||
// handleInternalEvent processes events from the internal client
|
||||
func (c *Client) handleInternalEvent(eventType string, data map[string]any) {
|
||||
switch eventType {
|
||||
case "connected":
|
||||
c.connected = true
|
||||
clientID := uint16(0)
|
||||
serverName := ""
|
||||
if v, ok := data["clientID"].(uint16); ok {
|
||||
clientID = v
|
||||
}
|
||||
if v, ok := data["serverName"].(string); ok {
|
||||
serverName = v
|
||||
}
|
||||
c.selfInfo = &SelfInfo{ClientID: clientID, Nickname: c.config.Nickname}
|
||||
c.emit(EventConnected, &ConnectedEvent{
|
||||
ClientID: clientID,
|
||||
ServerName: serverName,
|
||||
})
|
||||
|
||||
case "message":
|
||||
targetMode := MessageTarget(1)
|
||||
if v, ok := data["targetMode"].(int); ok {
|
||||
targetMode = MessageTarget(v)
|
||||
}
|
||||
c.emit(EventMessage, &MessageEvent{
|
||||
SenderID: getUint16(data, "senderID"),
|
||||
SenderName: getString(data, "senderName"),
|
||||
Message: getString(data, "message"),
|
||||
TargetMode: targetMode,
|
||||
})
|
||||
|
||||
case "client_enter":
|
||||
info := &ClientInfo{
|
||||
ID: getUint16(data, "clientID"),
|
||||
Nickname: getString(data, "nickname"),
|
||||
ChannelID: getUint64(data, "channelID"),
|
||||
}
|
||||
c.clientsMu.Lock()
|
||||
c.clients[info.ID] = info
|
||||
c.clientsMu.Unlock()
|
||||
|
||||
c.emit(EventClientEnter, &ClientEnterEvent{
|
||||
ClientID: info.ID,
|
||||
Nickname: info.Nickname,
|
||||
ChannelID: info.ChannelID,
|
||||
})
|
||||
|
||||
case "client_left":
|
||||
clientID := getUint16(data, "clientID")
|
||||
c.clientsMu.Lock()
|
||||
delete(c.clients, clientID)
|
||||
c.clientsMu.Unlock()
|
||||
|
||||
c.emit(EventClientLeft, &ClientLeftEvent{
|
||||
ClientID: clientID,
|
||||
Reason: getString(data, "reason"),
|
||||
})
|
||||
|
||||
case "client_moved":
|
||||
c.emit(EventClientMoved, &ClientMovedEvent{
|
||||
ClientID: getUint16(data, "clientID"),
|
||||
ChannelID: getUint64(data, "channelID"),
|
||||
})
|
||||
|
||||
case "channel_list":
|
||||
if channels, ok := data["channels"].([]*client.Channel); ok {
|
||||
c.channelsMu.Lock()
|
||||
var chList []*Channel
|
||||
for _, ch := range channels {
|
||||
converted := &Channel{
|
||||
ID: ch.ID,
|
||||
ParentID: ch.ParentID,
|
||||
Name: ch.Name,
|
||||
Order: ch.Order,
|
||||
}
|
||||
c.channels[ch.ID] = converted
|
||||
chList = append(chList, converted)
|
||||
}
|
||||
c.channelsMu.Unlock()
|
||||
c.emit(EventChannelList, &ChannelListEvent{Channels: chList})
|
||||
}
|
||||
|
||||
case "audio":
|
||||
c.emit(EventAudio, &AudioEvent{
|
||||
SenderID: getUint16(data, "senderID"),
|
||||
Codec: AudioCodec(getInt(data, "codec")),
|
||||
PCM: getPCM(data, "pcm"),
|
||||
Channels: getInt(data, "channels"),
|
||||
})
|
||||
|
||||
case "error":
|
||||
c.emit(EventError, &ErrorEvent{
|
||||
ID: getString(data, "id"),
|
||||
Message: getString(data, "message"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for type conversion
|
||||
func getString(m map[string]any, key string) string {
|
||||
if v, ok := m[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getUint16(m map[string]any, key string) uint16 {
|
||||
if v, ok := m[key].(uint16); ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := m[key].(int); ok {
|
||||
return uint16(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getUint64(m map[string]any, key string) uint64 {
|
||||
if v, ok := m[key].(uint64); ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := m[key].(int); ok {
|
||||
return uint64(v)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getInt(m map[string]any, key string) int {
|
||||
if v, ok := m[key].(int); ok {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getPCM(m map[string]any, key string) []int16 {
|
||||
if v, ok := m[key].([]int16); ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForConnection waits until the client is connected or timeout
|
||||
func (c *Client) WaitForConnection(timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if c.connected {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return fmt.Errorf("connection timeout")
|
||||
}
|
||||
237
pkg/ts3client/commands.go
Normal file
237
pkg/ts3client/commands.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package ts3client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
114
pkg/ts3client/events.go
Normal file
114
pkg/ts3client/events.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package ts3client
|
||||
|
||||
// EventType represents the type of event
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
// Connection events
|
||||
EventConnected EventType = "connected"
|
||||
EventDisconnected EventType = "disconnected"
|
||||
|
||||
// Message events
|
||||
EventMessage EventType = "message"
|
||||
|
||||
// Client events
|
||||
EventClientEnter EventType = "client_enter"
|
||||
EventClientLeft EventType = "client_left"
|
||||
EventClientMoved EventType = "client_moved"
|
||||
|
||||
// Channel events
|
||||
EventChannelList EventType = "channel_list"
|
||||
|
||||
// Audio events
|
||||
EventAudio EventType = "audio"
|
||||
|
||||
// Error events
|
||||
EventError EventType = "error"
|
||||
)
|
||||
|
||||
// ConnectedEvent is emitted when the client successfully connects
|
||||
type ConnectedEvent struct {
|
||||
ClientID uint16
|
||||
ServerName string
|
||||
}
|
||||
|
||||
// DisconnectedEvent is emitted when the client disconnects
|
||||
type DisconnectedEvent struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
// MessageEvent is emitted when a text message is received
|
||||
type MessageEvent struct {
|
||||
SenderID uint16
|
||||
SenderName string
|
||||
Message string
|
||||
TargetMode MessageTarget // Private, Channel, or Server
|
||||
}
|
||||
|
||||
// MessageTarget represents the target type of a message
|
||||
type MessageTarget int
|
||||
|
||||
const (
|
||||
MessageTargetPrivate MessageTarget = 1
|
||||
MessageTargetChannel MessageTarget = 2
|
||||
MessageTargetServer MessageTarget = 3
|
||||
)
|
||||
|
||||
func (m MessageTarget) String() string {
|
||||
switch m {
|
||||
case MessageTargetPrivate:
|
||||
return "Private"
|
||||
case MessageTargetChannel:
|
||||
return "Channel"
|
||||
case MessageTargetServer:
|
||||
return "Server"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ClientEnterEvent is emitted when a client enters the server
|
||||
type ClientEnterEvent struct {
|
||||
ClientID uint16
|
||||
Nickname string
|
||||
ChannelID uint64
|
||||
}
|
||||
|
||||
// ClientLeftEvent is emitted when a client leaves the server
|
||||
type ClientLeftEvent struct {
|
||||
ClientID uint16
|
||||
Reason string
|
||||
}
|
||||
|
||||
// ClientMovedEvent is emitted when a client moves to a different channel
|
||||
type ClientMovedEvent struct {
|
||||
ClientID uint16
|
||||
ChannelID uint64
|
||||
}
|
||||
|
||||
// ChannelListEvent is emitted when the channel list is received
|
||||
type ChannelListEvent struct {
|
||||
Channels []*Channel
|
||||
}
|
||||
|
||||
// AudioEvent is emitted when voice data is received
|
||||
type AudioEvent struct {
|
||||
SenderID uint16
|
||||
Codec AudioCodec
|
||||
PCM []int16 // Decoded PCM data (48kHz, mono or stereo)
|
||||
Channels int // 1 for mono, 2 for stereo
|
||||
}
|
||||
|
||||
// AudioCodec represents the audio codec type
|
||||
type AudioCodec int
|
||||
|
||||
const (
|
||||
CodecOpusVoice AudioCodec = 4
|
||||
CodecOpusMusic AudioCodec = 5
|
||||
)
|
||||
|
||||
// ErrorEvent is emitted when the server reports an error
|
||||
type ErrorEvent struct {
|
||||
ID string
|
||||
Message string
|
||||
}
|
||||
54
pkg/ts3client/types.go
Normal file
54
pkg/ts3client/types.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package ts3client
|
||||
|
||||
// Channel represents a TeamSpeak channel
|
||||
type Channel struct {
|
||||
ID uint64
|
||||
ParentID uint64
|
||||
Name string
|
||||
Order uint64
|
||||
}
|
||||
|
||||
// Client represents a connected client
|
||||
type ClientInfo struct {
|
||||
ID uint16
|
||||
Nickname string
|
||||
ChannelID uint64
|
||||
}
|
||||
|
||||
// ServerInfo contains server information
|
||||
type ServerInfo struct {
|
||||
Name string
|
||||
WelcomeMessage string
|
||||
Platform string
|
||||
Version string
|
||||
MaxClients int
|
||||
ClientsOnline int
|
||||
ChannelsOnline int
|
||||
}
|
||||
|
||||
// SelfInfo contains our own client information
|
||||
type SelfInfo struct {
|
||||
ClientID uint16
|
||||
Nickname string
|
||||
ChannelID uint64
|
||||
}
|
||||
|
||||
// Config contains client configuration options
|
||||
type Config struct {
|
||||
Nickname string
|
||||
SecurityLevel int // Default: 8
|
||||
Version string // Default: "3.6.2 [Build: 1690976575]"
|
||||
Platform string // Default: "Windows"
|
||||
HWID string // Default: generated
|
||||
}
|
||||
|
||||
// DefaultConfig returns a Config with sensible defaults
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Nickname: "GoTS3Bot",
|
||||
SecurityLevel: 8,
|
||||
Version: "3.6.2 [Build: 1690976575]",
|
||||
Platform: "Windows",
|
||||
HWID: "1234567890",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user