# teamspeak-go SDK 文档

基于 `github.com/honeybbq/teamspeak-go` 源码整理。

触发形式说明：

- **客户端请求** — 客户端主动发送命令到服务端，等待响应
- **服务端推送** — 服务端主动下发通知，客户端被动接收
- **本地调用** — 纯客户端本地操作，不涉及网络通信

> **关于"通过指令构建的能力"**: 部分 API 标注为"通过指令构建的能力"，表示其底层封装了 TS3 协议命令（如 `serverinfo`、`channelinfo`、`banlist` 等），通过 SDK 的 `ExecCommand` / `ExecCommandWithResponse` 基础设施发送并解析响应。这些命令与 SDK 原生内置的命令（如握手、事件通知）不同，是通过协议命令扩展出的额外能力，可参考对应的协议命令语法进行调试或扩展。

---

## 1. 连接管理

### 构造与连接

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `NewClient` | 本地调用 | `NewClient(identity, addr, nickname, ...options) *Client` | 创建客户端 | 创建 TeamSpeak 客户端实例。`identity` 为加密身份，`addr` 为服务器地址，`nickname` 为昵称，`options` 可选配置 |
| `Connect` | 客户端请求 | `Connect() error` | 连接服务器 | 发起 UDP 会话和握手连接 |
| `Disconnect` | 客户端请求 | `Disconnect() error` | 断开连接 | 优雅断开连接，发送 shutdown reason |
| `WaitConnected` | 本地调用 | `WaitConnected(ctx context.Context) error` | 等待连接就绪 | 阻塞等待握手完成，支持 context 取消。发送命令前必须先调用 |

### 连接选项（ClientOption）

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `WithLogger` | 本地调用 | `WithLogger(logger *slog.Logger)` | 设置日志 | 注入自定义 slog.Logger |
| `WithResolver` | 本地调用 | `WithResolver(r AddrResolver)` | 设置解析器 | 自定义 DNS/TSDNS 解析 |
| `WithServerPassword` | 本地调用 | `WithServerPassword(password string)` | 服务器密码 | 连接时使用的服务器密码 |
| `WithDefaultChannel` | 本地调用 | `WithDefaultChannel(channel string)` | 默认频道 | 连接后自动加入的频道名 |
| `WithDefaultChannelPassword` | 本地调用 | `WithDefaultChannelPassword(password string)` | 默认频道密码 | 默认频道的密码 |
| `WithCommandMiddleware` | 本地调用 | `WithCommandMiddleware(mw ...CommandMiddleware)` | 命令中间件 | 拦截/修改发送的命令 |
| `WithEventMiddleware` | 本地调用 | `WithEventMiddleware(mw ...EventMiddleware)` | 事件中间件 | 拦截/修改接收的事件 |

---

## 2. 事件注册

### 事件处理器

| 方法 | 触发形式 | 用法 | 作用 | 描述                                                     |
|------|----------|------|------|--------------------------------------------------------|
| `OnConnected` | 服务端推送 | `OnConnected(fn func())` | 连接成功 | 客户端完成握手后触发                                             |
| `OnDisconnected` | 服务端推送 | `OnDisconnected(fn func(error))` | 断开连接 | 连接断开时触发，携带错误原因                                         |
| `OnTextMessage` | 服务端推送 | `OnTextMessage(fn func(TextMessage))` | 收到消息 | 收到文本消息（私聊/频道/服务器），对应 `notifytextmessage`               |
| `OnClientEnter` | 服务端推送 | `OnClientEnter(fn func(ClientInfo))` | 用户进入 | 客户端进入视野（进入服务器），对应 `notifycliententerview`              |
| `OnClientLeave` | 服务端推送 | `OnClientLeave(fn func(ClientLeftViewEvent))` | 用户离开 | 客户端离开视野（离开服务器），对应 `notifyclientleftview`               |
| `OnClientMoved` | 服务端推送 | `OnClientMoved(fn func(ClientMovedEvent))` | 用户移动 | 客户端在频道间移动，对应 `notifyclientmoved`                       |
| `OnPoked` | 服务端推送 | `OnPoked(fn func(PokeEvent))` | 被戳一戳 | 收到其他用户的 Poke，对应 `notifyclientpoke`                     |
| `OnKicked` | 服务端推送 | `OnKicked(fn func(string))` | 被踢出 | 自己被踢出频道或服务器，从 `notifyclientleftview` 中 reasonid=4/5 触发 |
| `OnVoiceData` | 服务端推送 | `OnVoiceData(fn func(VoiceDataEvent))` | 收到语音 | 收到同频道其他客户端发送的 Opus 语音帧，通过 UDP 二进制包传输          |

### VoiceDataEvent 结构

收到语音数据的事件载荷，由 `OnVoiceData` 回调接收。

| 字段 | 类型 | 描述 |
|------|------|------|
| `ClientID` | `uint16` | 发送者客户端 ID |
| `Data` | `[]byte` | Opus 编码的语音帧原始数据 |
| `Codec` | `byte` | 编解码器类型：4 = Opus Voice（语音），5 = Opus Music（音乐） |

**语音包 UDP 二进制格式**（SDK 内部解析后填充 `VoiceDataEvent`）：

```
Offset  Size   Field
0       2      packetID（big-endian）
2       2      clientID（little-endian）
4       1      codec（4=Opus Voice, 5=Opus Music）
5       N      Opus 编码数据
```

**语音接收流程**：

```
Server UDP 语音包
  → PacketHandler 解密
  → handlePacket() 路由 PacketTypeVoice(0) / PacketTypeVoiceWhisper(1)
  → 解析 clientID、codec、opusData
  → notifyEvent(VoiceDataEvent{...})
  → startEventLoop 串行分发
  → OnVoiceData 注册的所有 handler 依次调用
```

---

## 3. 聊天命令

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `SendTextMessage` | 客户端请求 | `SendTextMessage(targetMode int, targetID uint64, msg string) error` | 发送文本消息 | `targetMode`: 1=私聊, 2=频道, 3=服务器。对应协议 `sendtextmessage` |
| `Poke` | 客户端请求 | `Poke(clid uint16, msg string) error` | 发送 Poke | 向指定用户发送戳一戳消息。对应协议 `clientpoke` |

### TextMessage 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `TargetMode` | `int` | 1=私聊, 2=频道, 3=服务器 |
| `Target` | `uint64` | 目标 ID（频道 ID 或客户端 ID） |
| `InvokerID` | `uint16` | 发送者客户端 ID |
| `InvokerName` | `string` | 发送者昵称 |
| `InvokerUID` | `string` | 发送者唯一标识 |
| `InvokerGroups` | `[]string` | 发送者所在组 |
| `Message` | `string` | 消息内容 |

### PokeEvent 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `InvokerID` | `uint16` | 发送者客户端 ID |
| `InvokerName` | `string` | 发送者昵称 |
| `InvokerUID` | `string` | 发送者唯一标识 |
| `Message` | `string` | Poke 消息内容 |

---

## 4. 客户端命令

### 基础客户端操作

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `ClientID` | 本地调用 | `ClientID() uint16` | 获取自身 ID | 返回服务器分配的客户端 ID（本地缓存） |
| `ListClients` | 客户端请求 | `ListClients() ([]ClientInfo, error)` | 在线用户列表 | 返回当前服务器所有在线客户端。对应协议 `clientlist` |
| `ClientMove` | 客户端请求 | `ClientMove(clid uint16, channelID uint64, password string) error` | 移动用户 | 将客户端移至指定频道。对应协议 `clientmove` |

### 客户端信息查询

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `clientinfo`、`clientdblist`、`clientdbfind`。

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `GetClientInfo` | 客户端请求 | `clientinfo clid=X` | `GetClientInfo(clid uint16) (map[string]string, error)` | 查询在线用户详情（原始 map） |
| `GetClientDetailInfo` | 客户端请求 | `clientinfo clid=X` | `GetClientDetailInfo(clid uint16) (*ClientDetailInfo, error)` | 查询在线用户详情（结构化） |
| `ListDBClients` | 客户端请求 | `clientdblist start=X duration=X` | `ListDBClients(start, duration int) ([]DBClient, error)` | 数据库客户端列表 |
| `FindClientByName` | 客户端请求 | `clientdbfind pattern=X -uid` | `FindClientByName(nickname string) (uid string, dbid uint64, err error)` | 按昵称搜索数据库用户 |
| `FindClientByDBID` | 客户端请求 | `clientdbfind -uid cldbid=X` | `FindClientByDBID(dbid uint64) (string, error)` | 按 DBID 查找 UID |

### 客户端状态与操作

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `clientupdate`、`clientkick`。

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `UpdateSelf` | 客户端请求 | `clientupdate` | `UpdateSelf(properties map[string]string) error` | 更新自身属性（昵称、away、静音等） |
| `KickClient` | 客户端请求 | `clientkick clid=X reasonid=X reasonmsg=X` | `KickClient(clid uint16, reasonID int, reasonMsg string) error` | 踢出用户（4=频道踢出, 5=服务器踢出） |

### ClientInfo 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `ID` | `uint16` | 客户端 ID |
| `Nickname` | `string` | 昵称 |
| `ChannelID` | `uint64` | 所在频道 ID |
| `UID` | `string` | 唯一标识 |
| `Type` | `int` | 客户端类型 |
| `ServerGroups` | `[]string` | 所在服务器组 |

### ClientDetailInfo 结构

通过 `clientinfo clid=X` 获取的完整客户端信息。比 `ClientInfo` 多出 away 状态、平台、版本、IP 等字段。

| 字段 | 类型 | 描述 |
|------|------|------|
| `ID` | `uint16` | 客户端 ID (clid) |
| `Nickname` | `string` | 昵称 |
| `UID` | `string` | 唯一标识 |
| `ChannelID` | `uint64` | 所在频道 ID |
| `Type` | `int` | 客户端类型 |
| `ServerGroups` | `[]string` | 所在服务器组 |
| `Away` | `bool` | 是否离开 |
| `AwayMessage` | `string` | 离开消息 |
| `InputMuted` | `bool` | 输入静音 |
| `OutputMuted` | `bool` | 输出静音 |
| `Platform` | `string` | 客户端平台 |
| `Version` | `string` | 客户端版本 |
| `IP` | `string` | 客户端 IP（需权限） |
| `Created` | `int64` | 首次连接时间（unix 时间戳） |
| `LastConnected` | `int64` | 最近连接时间 |
| `TotalConnections` | `int` | 总连接次数 |
| `Description` | `string` | 用户描述 |
| `IconID` | `int64` | 用户图标 ID |

### DBClient 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `DBID` | `uint64` | 数据库 ID (cldbid) |
| `UID` | `string` | 唯一标识 |
| `Nickname` | `string` | 昵称 |
| `Created` | `int64` | 首次连接时间 |
| `LastConnected` | `int64` | 最近连接时间 |
| `TotalConnections` | `int` | 总连接次数 |
| `Description` | `string` | 用户描述 |

### UpdateSelf 常用参数

| 参数 | 值 | 说明 |
|------|------|------|
| `client_nickname` | 字符串 | 新昵称 |
| `client_away` | `"1"` / `"0"` | 是否离开 |
| `client_away_message` | 字符串 | 离开消息 |
| `client_input_muted` | `"1"` / `"0"` | 输入静音 |
| `client_output_muted` | `"1"` / `"0"` | 输出静音 |
| `client_phonetic_nickname` | 字符串 | 语音昵称 |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `GetClientsJSON()` | `string` | 在线客户端列表（含 isSelf 标志） |
| `GetClientDetailInfoJSON(clid int)` | `string` | 单个客户端详细信息 |
| `ListDBClientsJSON(start, duration int)` | `string` | 数据库客户端列表 |
| `UpdateSelfJSON(propertiesJSON string)` | `string` | 更新自身（错误信息或空串） |
| `KickClient(clid int, reasonID int, reasonMsg string)` | `string` | 踢出用户（错误信息或空串） |

### ClientLeftViewEvent 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `ClientID` | `uint16` | 离开的客户端 ID |
| `ReasonID` | `int` | 原因：0=正常离开, 4=频道踢, 5=服务器踢 |
| `ReasonMessage` | `string` | 原因描述 |
| `IsSelf` | `bool` | 是否是自己 |

### ClientMovedEvent 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `ClientID` | `uint16` | 被移动的客户端 ID |
| `TargetChannelID` | `uint64` | 目标频道 ID |
| `ReasonID` | `int` | 原因 |
| `InvokerID` | `uint16` | 操作者 ID |
| `InvokerName` | `string` | 操作者昵称 |
| `InvokerUID` | `string` | 操作者唯一标识 |

---

## 5. 频道命令

### 基础频道列表

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `ListChannels` | 客户端请求 | `ListChannels() ([]ChannelInfo, error)` | 频道列表（基础） | 返回服务器所有频道的基础信息（ID、父频道、名称）。对应协议 `channellist` |

### 详细频道列表

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `ListChannelsDetailed` | 客户端请求 | `ListChannelsDetailed() ([]ChannelInfoDetailed, error)` | 频道列表（详细） | 返回服务器所有频道的完整属性。对应协议 `channellist -topic -flags -voice -limits -icon` |

`channellist` 命令支持的 flag 参数：

| Flag | 返回字段 | 说明 |
|------|---------|------|
| `-topic` | `channel_topic` | 频道主题描述 |
| `-flags` | `channel_flag_permanent`, `channel_flag_semi_permanent`, `channel_flag_default`, `channel_flag_password`, `channel_flag_maxclients_unlimited`, `channel_flag_maxfamilyclients_unlimited`, `channel_order` | 频道标志位（永久、半永久、默认、密码、人数限制、排序） |
| `-voice` | `channel_codec`, `channel_codec_quality`, `channel_needed_talk_power` | 语音编解码相关 |
| `-limits` | `channel_maxclients`, `channel_maxfamilyclients` | 频道人数限制 |
| `-icon` | `channel_icon_id` | 频道自定义图标 ID |

### 频道查询

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `channelinfo`、`channelfind`。

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `GetChannelInfo` | 客户端请求 | `channelinfo cid=X` | `GetChannelInfo(channelID uint64) (*ChannelDetailInfo, error)` | 单频道完整详情（含 description） |
| `FindChannels` | 客户端请求 | `channelfind pattern=X` | `FindChannels(pattern string) ([]ChannelInfo, error)` | 按名称搜索频道 |

### 频道管理

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `channelcreate`、`channeledit`、`channeldelete`、`channelmove`。

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `CreateChannel` | 客户端请求 | `channelcreate channel_name=X ...` | `CreateChannel(name string, options map[string]string) (uint64, error)` | 创建频道，返回新频道 ID |
| `EditChannel` | 客户端请求 | `channeledit cid=X ...` | `EditChannel(channelID uint64, properties map[string]string) error` | 编辑频道属性 |
| `DeleteChannel` | 客户端请求 | `channeldelete cid=X force=X` | `DeleteChannel(channelID uint64, force bool) error` | 删除频道，force=true 强制删除 |
| `MoveChannel` | 客户端请求 | `channelmove cid=X cpid=X order=X` | `MoveChannel(channelID, parentID, order uint64) error` | 移动频道到新父频道或调整排序 |

#### CreateChannel 常用可选参数

| 参数 | 值 | 说明 |
|------|------|------|
| `channel_topic` | 字符串 | 频道主题 |
| `channel_flag_permanent` | `"1"` / `"0"` | 永久频道 |
| `channel_flag_semi_permanent` | `"1"` / `"0"` | 半永久频道 |
| `channel_flag_default` | `"1"` / `"0"` | 默认频道 |
| `channel_password` | 字符串 | 频道密码 |
| `cpid` | 字符串（父频道ID） | 父频道 |
| `channel_maxclients` | 字符串 | 最大人数 |
| `channel_codec` | 字符串 | 编解码器（0-5） |

### ChannelInfo 结构（基础）

| 字段 | 类型 | 描述 |
|------|------|------|
| `ID` | `uint64` | 频道 ID |
| `ParentID` | `uint64` | 父频道 ID（0 = 顶层频道） |
| `Name` | `string` | 频道名称（已 Unescape） |
| `Description` | `string` | 频道描述（`ListChannels` 返回为空） |

### ChannelInfoDetailed 结构（详细列表项）

来自 `channellist -topic -flags -voice -limits -icon` 的批量列表项。

| 字段 | 类型 | 描述 |
|------|------|------|
| **基础** | | |
| `ID` | `uint64` | 频道 ID |
| `ParentID` | `uint64` | 父频道 ID（0 = 顶层频道） |
| `Order` | `uint64` | 排序顺序（前一个频道 ID，0 = 最顶部） |
| `Name` | `string` | 频道名称 |
| `Topic` | `string` | 频道主题（来自 `-topic`） |
| **语音（`-voice`）** | | |
| `Codec` | `int` | 0=Speex Narrowband, 1=Speex Wideband, 2=Speex UltraWideband, 3=CELT Mono, **4=Opus Voice**, **5=Opus Music** |
| `CodecQuality` | `int` | 编解码质量（0-10） |
| `NeededTalkPower` | `int` | 发言所需权限等级 |
| **限制（`-limits`）** | | |
| `MaxClients` | `int` | 最大客户端数 |
| `MaxFamilyClients` | `int` | 最大族客户端数 |
| `IsMaxClientsUnlimited` | `bool` | 是否无限人数 |
| `IsMaxFamilyClientsUnlimited` | `bool` | 是否无限族人数 |
| **标志（`-flags`）** | | |
| `IsPermanent` | `bool` | 永久频道 |
| `IsSemiPermanent` | `bool` | 半永久频道 |
| `IsDefault` | `bool` | 默认频道 |
| `IsPassword` | `bool` | 是否设置密码 |
| `IsOrdered` | `bool` | 是否手动排序 |
| `NeededModifyPower` | `int` | 修改频道所需权限 |
| **图标（`-icon`）** | | |
| `IconID` | `int64` | 频道图标 ID |

### ChannelDetailInfo 结构（单频道详情）

来自 `channelinfo cid=X` 的完整频道信息，比 `ChannelInfoDetailed` 多出 `Description`（完整描述）和 `BannerGfxURL` 等字段。

| 字段 | 类型 | 描述 |
|------|------|------|
| `ID` | `uint64` | 频道 ID |
| `ParentID` | `uint64` | 父频道 ID |
| `Name` | `string` | 频道名称 |
| `Topic` | `string` | 频道主题 |
| `Description` | `string` | 完整描述 |
| `Codec` | `int` | 编解码器 |
| `CodecQuality` | `int` | 编解码质量 |
| `MaxClients` | `int` | 最大人数 |
| `MaxFamilyClients` | `int` | 最大族人数 |
| `NeededTalkPower` | `int` | 发言权限 |
| `IconID` | `int64` | 图标 ID |
| `IsPermanent` | `bool` | 永久 |
| `IsSemiPermanent` | `bool` | 半永久 |
| `IsDefault` | `bool` | 默认 |
| `IsPassword` | `bool` | 有密码 |
| `Order` | `uint64` | 排序 |
| `BannerGfxURL` | `string` | Banner 图片 URL |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `GetChannelsJSON()` | `string` | 基础频道列表（向后兼容） |
| `GetChannelsDetailedJSON()` | `string` | 详细频道列表（含 flags/voice/limits/icon） |
| `GetChannelDetailInfoJSON(channelIDStr string)` | `string` | 单频道完整详情 |
| `CreateChannelJSON(name, propertiesJSON string)` | `string` | 创建频道，返回新频道 ID（空串=失败） |
| `EditChannelJSON(channelIDStr, propertiesJSON string)` | `string` | 编辑频道（错误信息或空串） |
| `DeleteChannel(channelIDStr string, force bool)` | `string` | 删除频道（错误信息或空串） |

`GetChannelsDetailedJSON()` 返回的 JSON 示例：

```json
[
  {
    "id": "1",
    "name": "Lobby",
    "parentId": "0",
    "topic": "欢迎来到大厅",
    "order": "0",
    "codec": 4,
    "codecQuality": 7,
    "neededTalkPower": 0,
    "maxClients": -1,
    "maxFamilyClients": -1,
    "isMaxClientsUnlimited": true,
    "isMaxFamilyClientsUnlimited": true,
    "isPermanent": true,
    "isSemiPermanent": false,
    "isDefault": true,
    "isPassword": false,
    "isOrdered": false,
    "iconId": "0",
    "neededModifyPower": 75
  }
]
```

### 典型用法

```go
// 获取详细频道列表
channels, err := client.ListChannelsDetailed()
if err != nil {
    return err
}

for _, ch := range channels {
    // 显示频道名和状态图标
    icons := ""
    if ch.IsPassword { icons += "🔒" }
    if ch.IsPermanent  { icons += "📌" }
    if !ch.IsMaxClientsUnlimited {
        icons += fmt.Sprintf(" 👥%d", ch.MaxClients)
    }
    log.Printf("%s %s %s", icons, ch.Name, ch.Topic)

    // 根据编解码器选择解码策略
    switch ch.Codec {
    case 4: // Opus Voice — 20ms 帧，适合语音
    case 5: // Opus Music — 更高采样率，适合音乐
    }
}
```

---

## 6. 语音命令

### 发送语音

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `SendVoice` | 客户端请求 | `SendVoice(data []byte, codec byte) error` | 发送语音帧 | 发送原始 Opus 帧。codec: 4=Opus 语音, 5=Opus 音乐。通过 UDP 传输 |

### 接收语音

接收语音通过事件回调实现，无需主动调用。

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `OnVoiceData` | 服务端推送 | `OnVoiceData(fn func(VoiceDataEvent))` | 注册语音接收回调 | 当同频道其他客户端发送语音时，SDK 解密后触发回调 |

**Codec 值说明**：

| 值 | 类型 | 适用场景 |
|----|------|---------|
| `4` | Opus Voice | 语音通话（默认） |
| `5` | Opus Music | 音乐/高保真音频 |

**典型用法**：

```go
client.OnVoiceData(func(evt teamspeak.VoiceDataEvent) {
    // evt.ClientID — 发送者客户端 ID
    // evt.Data     — Opus 编码帧，可直接送入解码器
    // evt.Codec    — 4=Opus Voice, 5=Opus Music
    decoded := opusDecoder.Decode(evt.Data, pcmBuffer)
    audioTrack.Write(pcmBuffer[:decoded])
})
```

**注意事项**：

- `OnVoiceData` 回调在事件循环 goroutine 中串行执行，**不要在回调中做耗时操作**（如 Opus 解码），应将数据推入 channel 由独立协程处理
- SDK 不内置 Opus 解码器，需要在应用层（Kotlin/Go）集成 `opus.Decode()`
- 语音帧以 20ms 为单位发送，采样率通常为 48kHz

---

## 7. 服务器查询

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `serverinfo`。

### 服务器信息

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `GetServerInfo` | 客户端请求 | `serverinfo` | `GetServerInfo() (*ServerInfo, error)` | 获取服务器完整信息 |

### ServerInfo 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `Name` | `string` | 服务器名称 |
| `WelcomeMessage` | `string` | 欢迎消息 |
| `MaxClients` | `int` | 最大客户端数 |
| `ClientsOnline` | `int` | 在线客户端数 |
| `ChannelsOnline` | `int` | 在线频道数 |
| `Uptime` | `int64` | 服务器运行时长（秒） |
| `Version` | `string` | 服务器版本 |
| `Platform` | `string` | 服务器平台 |
| `Created` | `int64` | 创建时间（unix 时间戳） |
| `IconID` | `int64` | 服务器图标 ID |
| `DefaultServerGroup` | `int` | 默认服务器组 ID |
| `DefaultChannelGroup` | `int` | 默认频道组 ID |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `GetServerInfoJSON()` | `string` | 服务器信息（JSON 对象，空 `"{}"` 表示未连接或出错） |

---

## 8. Ban 管理

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `banlist`、`banadd`、`bandel`、`bandelall`。

### Ban 操作

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `ListBans` | 客户端请求 | `banlist` | `ListBans() ([]BanEntry, error)` | 获取所有封禁记录 |
| `AddBan` | 客户端请求 | `banadd ip=X name=X uid=X time=X banreason=X` | `AddBan(ip, name, uid string, timeSeconds int, reason string) error` | 添加封禁（ip/name/uid 至少指定一个，time=0 为永久） |
| `DeleteBan` | 客户端请求 | `bandel banid=X` | `DeleteBan(banID int64) error` | 解除指定封禁 |
| `DeleteAllBans` | 客户端请求 | `bandelall` | `DeleteAllBans() error` | 清除所有封禁 |

### BanEntry 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `BanID` | `int64` | 封禁 ID |
| `IP` | `string` | IP（可能为空或部分掩码） |
| `Name` | `string` | 名称模式 |
| `UID` | `string` | 唯一标识 |
| `Created` | `int64` | 封禁时间（unix 时间戳） |
| `InvokerName` | `string` | 操作者昵称 |
| `InvokerUID` | `string` | 操作者 UID |
| `Reason` | `string` | 封禁原因 |
| `Enforcement` | `bool` | 是否立即执行 |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `ListBansJSON()` | `string` | Ban 列表 |
| `AddBan(ip, name, uid string, timeSeconds int, reason string)` | `string` | 添加封禁（错误信息或空串） |
| `DeleteBan(banIDStr string)` | `string` | 删除封禁（错误信息或空串） |

---

## 9. Token 管理

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `tokenlist`、`tokenuse`。

Token（权限密钥）用于让用户自动获得服务器组/频道组权限。

### Token 操作

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `ListTokens` | 客户端请求 | `tokenlist` | `ListTokens() ([]TokenEntry, error)` | 获取所有权限密钥 |
| `UseToken` | 客户端请求 | `tokenuse token=X` | `UseToken(token string) error` | 激活权限密钥 |

### TokenEntry 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `Token` | `string` | 权限密钥字符串 |
| `TokenType` | `int` | 类型：0=服务器组, 1=频道组 |
| `TokenID1` | `int64` | 组 ID |
| `TokenID2` | `int64` | 频道 ID（仅 token_type=1 时有效） |
| `Created` | `int64` | 创建时间（unix 时间戳） |
| `Description` | `string` | 描述 |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `ListTokensJSON()` | `string` | Token 列表 |
| `UseToken(token string)` | `string` | 使用 Token（错误信息或空串） |

---

## 10. 投诉管理

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `complainlist`、`complainadd`、`complaindel`。

### 投诉操作

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `ListComplaints` | 客户端请求 | `complainlist [tcldbid=X]` | `ListComplaints(targetDBID uint64) ([]ComplaintEntry, error)` | 查询投诉（targetDBID=0 查全部） |
| `AddComplaint` | 客户端请求 | `complainadd tcldbid=X message=X` | `AddComplaint(targetDBID uint64, message string) error` | 提交投诉 |
| `DeleteComplaint` | 客户端请求 | `complaindel tcldbid=X fcldbid=X` | `DeleteComplaint(targetDBID, fromDBID uint64) error` | 删除投诉 |

### ComplaintEntry 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `FromDBID` | `uint64` | 投诉者 DBID |
| `ToDBID` | `uint64` | 被投诉者 DBID |
| `Message` | `string` | 投诉内容 |
| `Timestamp` | `int64` | 投诉时间（unix 时间戳） |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `ListComplaintsJSON(targetDBIDStr string)` | `string` | 投诉列表（`"0"` 表示全部） |
| `AddComplaint(targetDBIDStr, message string)` | `string` | 提交投诉（错误信息或空串） |

---

## 11. 文件传输

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `FileTransferInitUpload` | 客户端请求 | `FileTransferInitUpload(channelID uint64, path string, password string, size uint64, overwrite bool) (*FileUploadInfo, error)` | 初始化上传 | 请求上传文件到频道文件目录。对应协议 `ftinitupload` |
| `FileTransferInitDownload` | 客户端请求 | `FileTransferInitDownload(channelID uint64, path string, password string) (*FileDownloadInfo, error)` | 初始化下载 | 请求下载频道文件。对应协议 `ftinitdownload` |
| `FileTransferDeleteFile` | 客户端请求 | `FileTransferDeleteFile(channelID uint64, paths []string) error` | 删除文件 | 删除频道中的文件。对应协议 `ftdeletefile` |

### 文件列表查询

> **通过指令构建的能力** — 底层封装 TS3 协议命令 `ftgetfilelist`。

| 方法 | 触发形式 | 协议命令 | 用法 | 作用 |
|------|----------|----------|------|------|
| `ListFiles` | 客户端请求 | `ftgetfilelist cid=X path=X` | `ListFiles(channelID uint64, path string) ([]FileEntry, error)` | 列出频道目录下的文件和子目录（path 为虚拟路径，根目录为 `/`） |

### FileEntry 结构

| 字段 | 类型 | 描述 |
|------|------|------|
| `Name` | `string` | 文件/目录名 |
| `Size` | `uint64` | 文件大小（字节，目录为 0） |
| `DateTime` | `int64` | 修改时间（unix 时间戳） |
| `IsFile` | `bool` | 是否为文件（false = 目录） |

### Bridge 层 JSON 接口

| 方法 | 返回 | 描述 |
|------|------|------|
| `ListFilesJSON(channelIDStr, path string)` | `string` | 频道文件列表 |

### 辅助函数

| 函数 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `DialFileTransfer` | 客户端请求 | `DialFileTransfer(host string, port uint16, key string) (net.Conn, error)` | 建立文件传输连接 | 建立到文件传输端口的 TCP 连接 |
| `UploadFileData` | 客户端请求 | `UploadFileData(host string, info *FileUploadInfo, data io.Reader) error` | 上传数据 | 通过已初始化的连接上传文件数据 |
| `DownloadFileData` | 客户端请求 | `DownloadFileData(host string, info *FileDownloadInfo, dest io.Writer) error` | 下载数据 | 通过已初始化的连接下载文件数据 |

---

## 12. 中间件

| 类型 | 触发形式 | 定义 | 作用 | 描述 |
|------|----------|------|------|------|
| `CommandMiddleware` | 本地调用 | `func(next func(string) error) func(string) error` | 命令中间件 | 拦截/修改即将发送的命令字符串 |
| `EventMiddleware` | 本地调用 | `func(next func(any)) func(any)` | 事件中间件 | 拦截/修改即将分发的事件 |

---

## 13. 解析器接口

| 类型 | 触发形式 | 定义 | 作用 | 描述 |
|------|----------|------|------|------|
| `AddrResolver` | 本地调用 | `Resolve(ctx context.Context, addr string) ([]discovery.ResolvedAddr, error)` | 地址解析器 | 自定义 DNS/TSDNS 服务器地址解析 |

---

## 14. 协议转义

| 函数 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `commands.Escape` | 本地调用 | `commands.Escape(s string) string` | 转义 | 将字符串转义为 TS3 协议安全格式 |
| `commands.Unescape` | 本地调用 | `commands.Unescape(s string) string` | 反转义 | 将 TS3 协议转义字符串还原 |
| `commands.BuildCommand` | 本地调用 | `commands.BuildCommand(cmd string, params map[string]string) string` | 构建命令 | 从命令名和参数 map 构建协议命令字符串 |
| `commands.BuildCommandOrdered` | 本地调用 | `commands.BuildCommandOrdered(cmd string, params [][2]string) string` | 构建命令（有序） | 同上，但保持参数顺序 |

---

## 15. 服务器通知（内部处理）

以下通知由 SDK 内部解析并转换为事件，开发者通过 `On*` 方法注册处理器即可，无需直接处理。

| 通知 ID | 触发形式 | 事件类型 | 描述                      |
|---------|----------|----------|-------------------------|
| `notifycliententerview` | 服务端推送 | `ClientInfo` | 客户端进入服务器(事件数据结构频道id未生效) |
| `notifyclientleftview` | 服务端推送 | `ClientLeftViewEvent` | 客户端离开视野（含踢出）            |
| `notifyclientmoved` | 服务端推送 | `ClientMovedEvent` | 客户端频道移动                 |
| `notifytextmessage` | 服务端推送 | `TextMessage` | 收到文本消息                  |
| `notifyclientpoke` | 服务端推送 | `PokeEvent` | 收到 Poke                 |
| `notifyclientneededpermissions` | 服务端推送 | — | 仅 Debug 日志，无事件          |
| `notifystartupload` | 服务端推送 | `FileUploadInfo` | 文件上传开始                  |
| `notifystartdownload` | 服务端推送 | `FileDownloadInfo` | 文件下载开始                  |
| `notifystatusfiletransfer` | 服务端推送 | `FileTransferStatusInfo` | 文件传输状态变更                |

---

## 16. 未实现的 TS3 协议命令

以下命令在 teamspeak-go 中尚未封装，如需支持可在 `api.go` 中按现有模式扩展（通过 `ExecCommand` / `ExecCommandWithResponse` 发送协议命令）。

### 用户管理

| 命令 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `clientdbedit` | 客户端请求 | `clientdbedit cldbid=X ...` | 编辑数据库用户 | 修改数据库用户属性（描述、昵称等） |
| `clientsetservergroup` | 客户端请求 | `clientsetservergroup cldbid=X sgid=X` | 设置服务器组 | 将用户添加到指定服务器组 |
| `clientgetdbidfromuid` | 客户端请求 | `clientgetdbidfromuid cluid=X` | UID→DBID | 从唯一标识获取数据库 ID |
| `clientgetnamefromuid` | 客户端请求 | `clientgetnamefromuid cluid=X` | UID→昵称 | 从唯一标识获取昵称 |
| `clientgetnamefromdbid` | 客户端请求 | `clientgetnamefromdbid cldbid=X` | DBID→昵称 | 从数据库 ID 获取昵称 |
| `clientgetids` | 客户端请求 | `clientgetids cluid=X` | UID→clid | 从唯一标识获取在线客户端 ID |

### 服务器组管理

| 命令 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `servergroupadd` | 客户端请求 | `servergroupadd name=X` | 创建服务器组 | 创建新的服务器组 |
| `servergroupdel` | 客户端请求 | `servergroupdel sgid=X force=1` | 删除服务器组 | 删除指定服务器组 |
| `servergroupaddclient` | 客户端请求 | `servergroupaddclient sgid=X cldbid=X` | 添加用户到组 | 将数据库用户添加到服务器组 |
| `servergroupdelclient` | 客户端请求 | `servergroupdelclient sgid=X cldbid=X` | 从组移除用户 | 将用户从服务器组移除 |
| `servergrouplist` | 客户端请求 | `servergrouplist` | 服务器组列表 | 获取所有服务器组 |

### 服务器管理

| 命令 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `serveredit` | 客户端请求 | `serveredit virtualserver_name=X ...` | 编辑服务器 | 修改服务器属性 |
| `serverprocessstop` | 客户端请求 | `serverprocessstop reasonmsg=X` | 关闭服务器 | 停止服务器进程 |

### 权限查询

| 命令 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `permoverview` | 客户端请求 | `permoverview cid=X cldbid=X` | 权限概览 | 获取用户在指定频道的权限概览 |
| `permget` | 客户端请求 | `permget permid=X` | 获取权限 | 获取指定权限的当前值 |
| `permfind` | 客户端请求 | `permfind permid=X` | 查找权限 | 查找拥有指定权限的所有对象 |

### 其他

| 命令 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `sendpluginmessage` | 客户端请求 | `sendpluginmessage target=X msg=X` | 插件消息 | 向指定目标发送插件消息 |
| `tokenadd` | 客户端请求 | `tokenadd tokentype=X ...` | 创建 Token | 创建新的权限 Token |
| `tokendelete` | 客户端请求 | `tokendelete token=X` | 删除 Token | 删除指定 Token |
| `banclient` | 客户端请求 | `banclient clid=X time=X banreason=X` | 踢+Ban | 踢出并封禁指定在线客户端 |

---

## 17. Bridge 层 Kotlin 友好 API（kotlin_api.go）

`go/teamspeak/kotlin_api.go` 是对 `bridge.go` 的补充封装，暴露 SDK 中已有但 bridge.go 未导出的能力，并提供 Identity 管理接口。

所有方法遵循 bridge.go 的 gomobile 导出约定：
- 返回 `string`：空字符串=成功，非空=错误信息
- 返回 JSON `string`：查询结果以 JSON 编码

### 17.1 Identity 管理

| 方法 | 触发形式 | 用法 | 作用 | 描述 |
|------|----------|------|------|------|
| `GenerateIdentity` | 本地调用 | `GenerateIdentity(securityLevel int) string` | 生成身份 | 生成加密身份并序列化为字符串。`securityLevel` 推荐值 8。Kotlin 侧应持久化返回值，后续通过 `ConnectWithIdentity` 复用 |
| `ConnectWithIdentity` | 客户端请求 | `ConnectWithIdentity(identityStr, host, nickname, password, defaultChannel, defaultChannelPassword string, cb EventCallback) string` | 用已有身份连接 | 使用已持久化的 identity 字符串连接。行为与 `Connect` 相同 |

**Identity 序列化格式**: `"base64EncodedPrivateKey:offset"`（由 SDK 的 `Identity.String()` 生成）

**典型用法**：

```kotlin
// 首次使用：生成并持久化
val identity = GenerateIdentity(8)
preferences.edit().putString("ts_identity", identity).apply()

// 后续使用：从持久化读取
val identity = preferences.getString("ts_identity", "") ?: ""
val error = client.connectWithIdentity(identity, host, nickname, password, "", "", callback)
```

### 17.2 查询能力（补充）

以下方法补充 bridge.go 中未暴露的 SDK 查询能力。

| 方法 | 返回 | 描述 |
|------|------|------|
| `FindChannelsJSON(pattern string) string` | JSON 数组 | 按名称搜索频道。返回 `[{"id":"1","name":"匹配的频道"}]` |
| `FindClientByNameJSON(nickname string) string` | JSON 对象 | 按昵称搜索数据库客户端。返回 `{"uid":"xxx","dbid":"123"}` |
| `FindClientByDBIDJSON(dbidStr string) string` | JSON 对象 | 按 DBID 查找客户端 UID。返回 `{"uid":"xxx"}` |

### 17.3 操作能力（补充）

以下方法补充 bridge.go 中未暴露的 SDK 操作能力。

| 方法 | 返回 | 描述 |
|------|------|------|
| `MoveClient(clientID int, channelIDStr, password string) string` | 错误信息 | 将指定客户端移动到目标频道 |
| `MoveChannel(channelIDStr, parentIDStr, orderStr string) string` | 错误信息 | 移动频道到新父频道或调整排序 |
| `DeleteAllBans() string` | 错误信息 | 清除所有封禁记录 |
| `DeleteComplaint(targetDBIDStr, fromDBIDStr string) string` | 错误信息 | 删除指定投诉 |

### 17.4 文件传输（初始化）

文件传输遵循三阶段流程：初始化 → TCP 连接 → 数据传输。以下方法封装初始化阶段。

| 方法 | 返回 | 描述 |
|------|------|------|
| `FileTransferInitUploadJSON(channelIDStr, path string, size int64, overwrite bool) string` | JSON 对象 | 初始化上传。返回 `{"port":0,"key":"...","clientFileTransferID":0,"serverFileTransferID":0,"seekPosition":0}` |
| `FileTransferInitDownloadJSON(channelIDStr, path string) string` | JSON 对象 | 初始化下载。返回 `{"port":0,"key":"...","size":0,"clientFileTransferID":0,"serverFileTransferID":0}` |
| `DeleteFile(channelIDStr, pathsJSON string) string` | 错误信息 | 删除频道文件。`pathsJSON` 为 JSON 数组如 `["/file1.txt","/file2.txt"]` |

**注意**：TCP 连接的 host 为当前服务器地址，port 从初始化返回的 JSON 中获取。完整的文件传输流程（TCP 连接 + 数据传输）需要额外封装。

### 17.5 批量查询（首次同步优化）

| 方法 | 返回 | 描述 |
|------|------|------|
| `GetInitialSyncJSON() string` | JSON 对象 | 一次性返回首次同步所需的全部数据，减少 JNI 调用次数 |

返回格式：

```json
{
  "channels": [
    {"id":"1","name":"Lobby","parentId":"0","topic":"","order":"0","codec":4,"codecQuality":7,...}
  ],
  "clients": [
    {"id":1,"nickname":"User","uid":"xxx","channelId":"1","serverGroups":[],"isSelf":true}
  ],
  "selfId": 1,
  "selfChannelId": "1",
  "server": {
    "name":"My Server","welcomeMessage":"","maxClients":100,"clientsOnline":5,"channelsOnline":3,...
  }
}
```

任一子查询失败时对应字段为 null/空数组，不影响其他字段。

### 17.6 Bridge 层方法完整索引

#### bridge.go 已有方法

| 分类 | 方法 | 返回 |
|------|------|------|
| 连接 | `Connect(host, nickname, password, defaultChannel, defaultChannelPassword, cb)` | 错误信息 |
| 连接 | `Disconnect()` | — |
| 连接 | `IsConnected()` | bool |
| 连接 | `GetClientID()` | int |
| 连接 | `GetChannelID()` | string |
| 查询 | `GetChannelsJSON()` | JSON 数组 |
| 查询 | `GetChannelsDetailedJSON()` | JSON 数组 |
| 查询 | `GetClientsJSON()` | JSON 数组 |
| 查询 | `GetServerInfoJSON()` | JSON 对象 |
| 查询 | `GetChannelDetailInfoJSON(channelIDStr)` | JSON 对象 |
| 查询 | `GetClientDetailInfoJSON(clid)` | JSON 对象 |
| 查询 | `ListDBClientsJSON(start, duration)` | JSON 数组 |
| 查询 | `ListBansJSON()` | JSON 数组 |
| 查询 | `ListTokensJSON()` | JSON 数组 |
| 查询 | `ListFilesJSON(channelIDStr, path)` | JSON 数组 |
| 查询 | `ListComplaintsJSON(targetDBIDStr)` | JSON 数组 |
| 操作 | `SendChannelMessage(channelIDStr, message)` | 错误信息 |
| 操作 | `SendTextMessage(targetMode, targetIDStr, message)` | 错误信息 |
| 操作 | `MoveToChannel(channelIDStr, password)` | 错误信息 |
| 操作 | `Poke(clidStr, message)` | 错误信息 |
| 操作 | `SendVoice(data, codec)` | 错误信息 |
| 操作 | `CreateChannelJSON(name, propertiesJSON)` | 新频道 ID |
| 操作 | `EditChannelJSON(channelIDStr, propertiesJSON)` | 错误信息 |
| 操作 | `DeleteChannel(channelIDStr, force)` | 错误信息 |
| 操作 | `UpdateSelfJSON(propertiesJSON)` | 错误信息 |
| 操作 | `KickClient(clid, reasonID, reasonMsg)` | 错误信息 |
| 操作 | `AddBan(ip, name, uid, timeSeconds, reason)` | 错误信息 |
| 操作 | `DeleteBan(banIDStr)` | 错误信息 |
| 操作 | `UseToken(token)` | 错误信息 |
| 操作 | `AddComplaint(targetDBIDStr, message)` | 错误信息 |

#### kotlin_api.go 新增方法

| 分类 | 方法 | 返回 |
|------|------|------|
| Identity | `GenerateIdentity(securityLevel)` | 序列化的 identity 字符串 |
| Identity | `ConnectWithIdentity(identityStr, host, nickname, password, defaultChannel, defaultChannelPassword, cb)` | 错误信息 |
| 查询 | `FindChannelsJSON(pattern)` | JSON 数组 |
| 查询 | `FindClientByNameJSON(nickname)` | JSON 对象 |
| 查询 | `FindClientByDBIDJSON(dbidStr)` | JSON 对象 |
| 操作 | `MoveClient(clientID, channelIDStr, password)` | 错误信息 |
| 操作 | `MoveChannel(channelIDStr, parentIDStr, orderStr)` | 错误信息 |
| 操作 | `DeleteAllBans()` | 错误信息 |
| 操作 | `DeleteComplaint(targetDBIDStr, fromDBIDStr)` | 错误信息 |
| 文件 | `FileTransferInitUploadJSON(channelIDStr, path, size, overwrite)` | JSON 对象 |
| 文件 | `FileTransferInitDownloadJSON(channelIDStr, path)` | JSON 对象 |
| 文件 | `DeleteFile(channelIDStr, pathsJSON)` | 错误信息 |
| 批量 | `GetInitialSyncJSON()` | JSON 对象 |
