224 lines
7.7 KiB
Markdown
224 lines
7.7 KiB
Markdown
<div align="center">
|
|
|
|
# teamspeak-go
|
|
|
|
**A clean-room TeamSpeak client protocol library written in pure Go.**
|
|
|
|
Compatible with TeamSpeak 3, 5 & 6. No proprietary SDK. No copy-pasted code.
|
|
|
|
[](https://github.com/honeybbq/teamspeak-go/actions/workflows/ci.yml)
|
|
[](https://codecov.io/gh/honeybbq/teamspeak-go)
|
|
[](https://goreportcard.com/report/github.com/honeybbq/teamspeak-go)
|
|
|
|
[](https://pkg.go.dev/github.com/honeybbq/teamspeak-go)
|
|
[](go.mod)
|
|
[](LICENSE)
|
|
|
|
</div>
|
|
|
|
## Features
|
|
|
|
- **Full protocol handshake** — ECDH key exchange, RSA puzzle, EAX-encrypted transport
|
|
- **Command & notification system** — Send commands, receive server events
|
|
- **Event-driven API** — Register handlers for text messages, client enter/leave, channel moves, kicks, etc.
|
|
- **Voice data** — Send Opus voice packets (codec 4 & 5)
|
|
- **File transfers** — Upload, download, and delete files on the server
|
|
- **Address resolution** — SRV records, TSDNS, and direct address support
|
|
- **Middleware** — Pluggable command and event middleware chains
|
|
- **Built-in rate limiter** — Token-bucket throttling to prevent server-side flood kicks
|
|
- **Identity management** — Generate, import/export, and upgrade security level of identities
|
|
- **Zero CGO** — Pure Go, cross-compile anywhere
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
go get github.com/honeybbq/teamspeak-go
|
|
```
|
|
|
|
Requires **Go 1.26** or later.
|
|
|
|
## Quick Start
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
teamspeak "github.com/honeybbq/teamspeak-go"
|
|
"github.com/honeybbq/teamspeak-go/crypto"
|
|
)
|
|
|
|
func main() {
|
|
// Generate a new identity (or load an existing one)
|
|
identity, err := crypto.GenerateIdentity(8)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Create the client
|
|
client := teamspeak.NewClient(
|
|
identity,
|
|
"localhost",
|
|
"GoBot",
|
|
teamspeak.WithServerPassword(os.Getenv("TEAMSPEAK_SERVER_PASSWORD")),
|
|
teamspeak.WithDefaultChannel("Lobby"),
|
|
teamspeak.WithDefaultChannelPassword(os.Getenv("TEAMSPEAK_DEFAULT_CHANNEL_PASSWORD")),
|
|
)
|
|
|
|
// Register event handlers
|
|
client.OnConnected(func() {
|
|
fmt.Println("Connected to server!")
|
|
})
|
|
|
|
client.OnTextMessage(func(msg teamspeak.TextMessage) {
|
|
fmt.Printf("[%s]: %s\n", msg.InvokerName, msg.Message)
|
|
})
|
|
|
|
client.OnDisconnected(func(err error) {
|
|
fmt.Println("Disconnected:", err)
|
|
})
|
|
|
|
// Connect
|
|
if err := client.Connect(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Wait until connected
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
if err := client.WaitConnected(ctx); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Wait for interrupt signal
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, os.Interrupt)
|
|
<-sig
|
|
|
|
client.Disconnect()
|
|
}
|
|
```
|
|
|
|
## API Overview
|
|
|
|
### Client Lifecycle
|
|
|
|
| Method | Description |
|
|
|---|---|
|
|
| `NewClient(identity, addr, nickname, ...opts)` | Create a new client |
|
|
| `Connect()` | Initiate connection to the server |
|
|
| `WaitConnected(ctx)` | Block until the handshake completes |
|
|
| `Disconnect()` | Gracefully disconnect |
|
|
|
|
### Events
|
|
|
|
| Method | Description |
|
|
|---|---|
|
|
| `OnConnected(func())` | Fires when fully connected |
|
|
| `OnDisconnected(func(error))` | Fires on disconnect |
|
|
| `OnTextMessage(func(TextMessage))` | Fires on text messages |
|
|
| `OnClientEnter(func(ClientInfo))` | Fires when a client joins |
|
|
| `OnClientLeave(func(ClientLeftViewEvent))` | Fires when a client leaves |
|
|
| `OnClientMoved(func(ClientMovedEvent))` | Fires when a client moves channels |
|
|
| `OnKicked(func(string))` | Fires when the bot is kicked |
|
|
|
|
### Commands
|
|
|
|
| Method | Description |
|
|
|---|---|
|
|
| `SendTextMessage(targetMode, targetID, msg)` | Send a text message |
|
|
| `ClientMove(clid, channelID, password)` | Move a client to a channel |
|
|
| `Poke(clid, message)` | Poke a client |
|
|
| `SendVoice(data, codec)` | Send Opus voice data |
|
|
| `ListChannels()` | List all channels |
|
|
| `ListClients()` | List all connected clients |
|
|
| `GetClientInfo(clid)` | Get detailed client information |
|
|
| `ExecCommand(cmd, timeout)` | Execute a raw command |
|
|
| `ExecCommandWithResponse(cmd, timeout)` | Execute a command and return response data |
|
|
|
|
### File Transfers
|
|
|
|
| Method | Description |
|
|
|---|---|
|
|
| `FileTransferInitUpload(...)` | Initialize a file upload |
|
|
| `FileTransferInitDownload(...)` | Initialize a file download |
|
|
| `FileTransferDeleteFile(...)` | Delete files on the server |
|
|
| `UploadFileData(host, info, reader)` | Transfer file data to the server |
|
|
| `DownloadFileData(host, info, writer)` | Receive file data from the server |
|
|
|
|
### Identity
|
|
|
|
```go
|
|
// Generate a new identity with security level 8
|
|
identity, err := crypto.GenerateIdentity(8)
|
|
|
|
// Export to string for persistent storage
|
|
exported := identity.String()
|
|
|
|
// Import from a previously exported string
|
|
identity, err = crypto.IdentityFromString(exported)
|
|
|
|
// Upgrade security level (CPU-intensive)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
err = identity.UpgradeToLevel(10, ctx)
|
|
```
|
|
|
|
### Options
|
|
|
|
```go
|
|
client := teamspeak.NewClient(identity, "ts.example.com", "Bot",
|
|
teamspeak.WithLogger(slog.Default()),
|
|
teamspeak.WithResolver(customResolver),
|
|
teamspeak.WithCommandMiddleware(loggingMiddleware),
|
|
teamspeak.WithEventMiddleware(filterMiddleware),
|
|
teamspeak.WithServerPassword(os.Getenv("TEAMSPEAK_SERVER_PASSWORD")),
|
|
teamspeak.WithDefaultChannel("Lobby"),
|
|
teamspeak.WithDefaultChannelPassword(os.Getenv("TEAMSPEAK_DEFAULT_CHANNEL_PASSWORD")),
|
|
)
|
|
```
|
|
|
|
Connection auth options:
|
|
|
|
- `WithServerPassword(password)` accepts the plain-text server password and sends the TeamSpeak protocol hash during `clientinit`
|
|
- `WithDefaultChannel(channel)` requests a default channel during `clientinit`
|
|
- `WithDefaultChannelPassword(password)` accepts the plain-text channel password and sends the TeamSpeak protocol hash for the configured default channel
|
|
|
|
## Architecture
|
|
|
|
```
|
|
teamspeak-go/
|
|
├── client.go # Client lifecycle, connection management
|
|
├── api.go # High-level API (messages, channels, clients)
|
|
├── commands.go # Command sending and response tracking
|
|
├── events.go # Event handler registration and middleware
|
|
├── notifications.go # Server notification parsing and dispatch
|
|
├── handshake.go # Protocol handshake orchestration
|
|
├── transfer.go # File transfer operations
|
|
├── crypto/ # ECDH, EAX encryption, identity management
|
|
├── handshake/ # Crypto handshake and license verification
|
|
├── transport/ # UDP packet framing, ACK, compression
|
|
├── commands/ # Command builder and parser
|
|
└── discovery/ # SRV / TSDNS / direct address resolution
|
|
```
|
|
|
|
## Acknowledgments
|
|
|
|
Protocol knowledge was primarily informed by the [TSLib](https://github.com/Splamy/TS3AudioBot) implementation in [TS3AudioBot](https://github.com/Splamy/TS3AudioBot) by Splamy. Huge thanks to the TS3AudioBot project and its contributors.
|
|
|
|
## Disclaimer
|
|
|
|
TeamSpeak is a registered trademark of [TeamSpeak Systems GmbH](https://teamspeak.com/). This project is not affiliated with, endorsed by, or associated with TeamSpeak Systems GmbH in any way.
|
|
|
|
This library is a **clean-room implementation** developed from publicly available documentation, protocol analysis of network traffic, and independent research. No proprietary TeamSpeak SDK code, headers, or libraries were used in its creation.
|
|
|
|
## License
|
|
|
|
[MIT](LICENSE)
|