Files
ts-mobile-go/go/_patches/github.com/honeybbq/teamspeak-go/CLAUDE.md
T
2026-07-20 19:01:03 +08:00

17 KiB
Raw Blame History

teamspeak-go

纯 Go 实现的 TeamSpeak 3 客户端协议库,零 CGO 依赖,支持 gomobile 编译为 Android .aar

Architecture

┌──────────────────────────────────────────────┐
│  Application Layer                           │
│  (bridge.go / 用户代码)                       │
│  OnConnected, OnTextMessage, SendVoice ...   │
├──────────────────────────────────────────────┤
│  Client (client.go)                          │
│  连接管理、事件分发、命令追踪                    │
│  events.go — 事件注册 & dispatchEvent         │
│  commands.go — 命令发送 & return_code 匹配     │
│  notifications.go — notify* 事件解析           │
│  api.go — 高层 API (ListChannels, SendText...)│
│  handshake.go — 加密握手 & clientinit          │
│  transfer.go — 文件传输                        │
│  throttle.go — 命令限速 (token bucket)         │
├──────────────────────────────────────────────┤
│  Transport (transport/)                      │
│  PacketHandler — UDP 收发、包分片              │
│  packet.go — 包类型定义                        │
│  quicklz.go — QuickLZ 压缩解压                │
├──────────────────────────────────────────────┤
│  Crypto (crypto/)                            │
│  Identity 生成、密钥交换、EAX 加密              │
├──────────────────────────────────────────────┤
│  Handshake (handshake/)                      │
│  crypt_init2.go — 二次加密协商                 │
│  license.go — 许可证验证                       │
├──────────────────────────────────────────────┤
│  Discovery (discovery/)                      │
│  SRV / TSDNS / 直连 地址解析                   │
└──────────────────────────────────────────────┘

Key Files

文件 职责
client.go Client 结构体、NewClientConnectDisconnect、事件循环 (startEventLoop)、notifyEvent 入队
events.go 事件注册 API (OnConnected, OnTextMessage 等)、dispatchEvent 分发到所有 handler
notifications.go handleNotification — 解析 notify* 命令,转换为结构体事件
commands.go handlePacket — 包类型路由、ExecCommand / ExecCommandWithResponse — 带 return_code 的异步命令
api.go 高层 APIListChannelsListClientsGetClientInfoSendTextMessageClientMovePokeSendVoiceWaitConnected
handshake.go handleHandshakeInitIVhandleHandshakeExpand2handleInitServersendClientInit
types.go 所有事件/数据结构体定义
transfer.go 文件传输:FileTransferInitUploadFileTransferInitDownloadFileTransferDeleteFile
throttle.go Token-bucket 命令限速器(4 tokens/s,上限 8

Event Loop

所有事件在单个 goroutine 中串行处理,保证按到达顺序分发:

notifyEvent(evt)          ← 任何 goroutine 调用
  → 入队 (evtQueueMu)
  → signal (evtCond)
      ↓
startEventLoop goroutine  ← 唯一消费者
  → 取出全部待处理事件
  → dispatchEvent(evt)    ← 逐个分发到注册的 handler

Event Lifecycle

事件分为两类:服务器通知事件notify 前缀,服务器主动推送)和内部事件SDK 状态变化触发)。

Phase 1: Connection (连接阶段)

客户端从发起到建立连接的握手过程,涉及加密协商和身份初始化。

事件名 类型 作用 描述
PacketTypeInit1 握手包 初始化加密通道 客户端发送 UDP 包后服务器返回 init1 响应,完成第一阶段密钥交换。SDK 内部处理,应用层无感
clientinitiv 服务器响应 加密参数协商 服务器返回 alphabetaomega 加密参数,客户端调用 InitCrypto 初始化加密上下文
initivexpand2 服务器响应 二次加密扩展 服务器返回许可证和扩展密钥,客户端生成临时密钥对(clientek)完成最终加密握手
clientinit 客户端命令 发送身份信息 加密握手完成后,客户端发送昵称、版本、HWID、默认频道等信息,请求加入服务器
initserver 服务器响应 连接建立确认 服务器分配客户端 IDclid),连接正式建立。触发 OnConnected 回调
OnConnected SDK 回调 连接成功通知 应用层注册的连接成功回调。SDK 自动发送 clientupdate 解除静音。此时可调用 ListChannelsListClients 获取快照

Phase 2: Runtime — User Events (用户事件)

服务器上用户的上下线、移动、消息等实时事件。

事件名 类型 作用 描述
notifycliententerviewOnClientEnter 服务器通知 用户进入视野 有新客户端连接到服务器或进入可见范围。数据结构 ClientInfoIDclid)、NicknameUIDChannelIDTypeServerGroups
notifyclientleftviewOnClientLeave 服务器通知 用户离开视野 客户端断开连接或离开可见范围。数据结构 ClientLeftViewEventIDReasonID(0=正常、4=频道踢出、5=服务器踢出)、ReasonMsg
notifyclientmovedOnClientMoved 服务器通知 用户切换频道 客户端被移动到另一个频道。数据结构 ClientMovedEventIDTargetChannelIDReasonIDInvokerIDInvokerNameInvokerUID如果是自己被移动,这是频道切换的确认点
notifytextmessageOnTextMessage 服务器通知 收到文字消息 收到私聊/频道/服务器范围的文字消息。数据结构 TextMessageTargetMode1=私聊、2=频道、3=服务器)、TargetIDInvokerIDInvokerNameInvokerUIDMessageInvokerGroups
notifyclientpokeOnPoked 服务器通知 被其他用户戳 有用户发送 poke 消息。数据结构 PokeEventInvokerIDInvokerNameInvokerUIDMessage
notifyclientneededpermissions 服务器通知 权限不足 操作因权限不足被拒绝。携带 permidpermvalue。SDK 仅记录日志,不触发应用层回调

Phase 3: Runtime — Voice (语音事件)

实时语音数据的收发,通过 UDP 二进制包传输。

事件名 类型 作用 描述
VoiceDataEventOnVoiceData 二进制包 收到语音数据 其他客户端发送的 Opus 语音帧。数据结构 VoiceDataEventClientID(发送者)、DataOpus 数据)、Codec4=Opus Voice、5=Opus Music
SendVoice(data, codec) 客户端命令 发送语音数据 发送 Opus 编码的语音帧到服务器,服务器转发给同频道其他用户。通过 handler.SendVoicePacket 直接写入 UDP

语音包二进制格式:

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 encoded data

Phase 4: Runtime — File Transfer (文件传输事件)

服务器文件的上传、下载和管理。文件数据通过独立的 TCP 连接传输。

事件名 类型 作用 描述
ftinitupload / notifystartupload 命令+通知 初始化文件上传 客户端请求上传文件,服务器返回 FileUploadInfoPortTCP 端口)、FileTransferKey(传输密钥)、SeekPosition(断点)、ClientFileTransferIDServerFileTransferID
ftinitdownload / notifystartdownload 命令+通知 初始化文件下载 客户端请求下载文件,服务器返回 FileDownloadInfoPortFileTransferKeySizeClientFileTransferIDServerFileTransferID
notifystatusfiletransfer 服务器通知 文件传输状态 传输完成或失败的状态通知。数据结构 FileTransferStatusInfoStatus0=成功)、Message(错误信息)、ClientFileTransferID
ftdeletefile 客户端命令 删除服务器文件 删除指定频道目录下的文件,支持 | 分隔的批量删除

Phase 5: Runtime — Commands & Errors (命令与错误)

客户端命令的异步响应机制和错误处理。

事件名 类型 作用 描述
return_code 服务器响应 命令执行结果 每个命令附带 return_code 参数,服务器执行完成后返回对应的 return_codeSDK 通过 commandTracker 匹配异步响应。id=0 表示成功,非 0 为错误码
error 服务器响应 服务器错误 服务器返回的错误信息。id(错误码)、msg(错误描述)。错误码 3329 为致命错误(如被 ban),SDK 自动断开连接
clientupdate 客户端命令 更新客户端状态 连接成功后 SDK 自动发送,设置 client_input_muted=0client_output_muted=0

命令限速: SDK 内置 token-bucket 限速器(throttle.go),速率 4 tokens/s,上限 8。所有 ExecCommand 调用自动排队等待。

Phase 6: Disconnect (断开阶段)

连接关闭和清理。

事件名 类型 作用 描述
clientdisconnect 客户端命令 主动断开 客户端发送 clientdisconnect reasonmsg=Shutdown,然后关闭 UDP 连接
OnDisconnected SDK 回调 断开连接通知 连接断开时触发。参数 errornil = 正常断开,非 nil = 异常断开原因
OnClosed (handler) 传输层回调 底层连接关闭 UDP 连接关闭时触发,SDK 内部据此调用 OnDisconnected。应用层不直接使用

Lifecycle Overview (生命周期总览)

NewClient(identity, addr, nickname, opts...)
  │
  ├─ 启动事件循环协程 (startEventLoop)
  ├─ 初始化 PacketHandler、Crypto、Throttle
  │
  Connect()
  │
  ├─ 地址解析 (SRV / TSDNS / 直连)
  ├─ UDP 连接
  ├─ PacketTypeInit1 握手
  ├─ ← clientinitiv (加密参数)
  ├─ → InitCrypto
  ├─ ← initivexpand2 (二次加密)
  ├─ → clientek (临时密钥)
  ├─ → clientinit (身份信息)
  ├─ ← initserver → 连接建立
  │
  ├─ → OnConnected() 回调
  ├─ → clientupdate (解除静音)
  │
  │   ┌─ API 调用 ─────────────────────────────┐
  │   │ ListChannels()  → channellist           │
  │   │ ListClients()   → clientlist            │
  │   │ GetClientInfo() → clientinfo            │
  │   │ SendTextMessage() → sendtextmessage     │
  │   │ ClientMove()    → clientmove            │
  │   │ SendVoice()     → UDP 二进制包           │
  │   └─────────────────────────────────────────┘
  │
  │   ┌─ 服务器事件 ────────────────────────────┐
  │   │ notifycliententerview → OnClientEnter   │
  │   │ notifyclientleftview  → OnClientLeave   │
  │   │ notifyclientmoved     → OnClientMoved   │
  │   │ notifytextmessage     → OnTextMessage   │
  │   │ notifyclientpoke      → OnPoked         │
  │   │ VoiceDataEvent        → OnVoiceData     │
  │   │ error (id=3329)       → 自动断开         │
  │   └─────────────────────────────────────────┘
  │
  Disconnect()
  │
  ├─ → clientdisconnect reasonmsg=Shutdown
  ├─ → 关闭 UDP 连接 (handler.Close)
  └─ → OnDisconnected(nil) 回调

Data Types

结构体 用途 关键字段
ClientInfo 客户端信息 ID (uint16), Nickname, UID, ChannelID (uint64), Type, ServerGroups ([]string)
ChannelInfo 频道信息 ID (uint64), Name, ParentID (uint64), Description
TextMessage 文字消息 TargetMode (int), TargetID (uint64), InvokerID (uint16), InvokerName, InvokerUID, Message, InvokerGroups
ClientMovedEvent 频道移动 ID (uint16), TargetChannelID (uint64), ReasonID (int), InvokerID (uint16), InvokerName, InvokerUID
ClientLeftViewEvent 用户离开 ID (uint16), ReasonID (int), ReasonMsg, TargetID (uint16)
PokeEvent Poke 消息 InvokerID (uint16), InvokerName, InvokerUID, Message
VoiceDataEvent 语音数据 ClientID (uint16), Data ([]byte), Codec (byte)
FileUploadInfo 上传信息 Port, FileTransferKey, SeekPosition, ClientFileTransferID, ServerFileTransferID
FileDownloadInfo 下载信息 Port, FileTransferKey, Size, ClientFileTransferID, ServerFileTransferID
FileTransferStatusInfo 传输状态 Status (int), Message, ClientFileTransferID

API Reference

Client Construction

identity, _ := crypto.GenerateIdentity(8)
client := teamspeak.NewClient(identity, "host:9987", "Nickname",
    teamspeak.WithServerPassword("pass"),
    teamspeak.WithDefaultChannel("/General"),
    teamspeak.WithDefaultChannelPassword("cpw"),
    teamspeak.WithLogger(logger),
    teamspeak.WithResolver(customResolver),
)

Connection

client.Connect()                          // 启动连接(非阻塞)
client.WaitConnected(context.Background()) // 阻塞等待握手完成
client.Disconnect()                       // 优雅断开
client.ClientID()                         // 获取服务器分配的客户端 ID

Data Queries

channels, err := client.ListChannels()    // 获取所有频道
clients, err := client.ListClients()      // 获取所有在线客户端
info, err := client.GetClientInfo(clid)   // 获取单个客户端详情

Messaging

client.SendTextMessage(targetMode, targetID, message) // 发送文字消息
client.ClientMove(clid, channelID, password)           // 移动频道
client.Poke(clid, message)                             // Poke 用户

Voice

client.SendVoice(opusData, 4) // 发送 Opus Voice 帧 (codec=4)
client.SendVoice(opusData, 5) // 发送 Opus Music 帧 (codec=5)

File Transfer

info, err := client.FileTransferInitUpload(channelID, "/path", "", size, false)
teamspeak.UploadFileData(host, info, reader)

info, err := client.FileTransferInitDownload(channelID, "/path", "")
teamspeak.DownloadFileData(host, info, writer)

client.FileTransferDeleteFile(channelID, []string{"/file1", "/file2"})

Event Registration

client.OnConnected(func() { ... })
client.OnDisconnected(func(err error) { ... })
client.OnClientEnter(func(info teamspeak.ClientInfo) { ... })
client.OnClientLeave(func(evt teamspeak.ClientLeftViewEvent) { ... })
client.OnClientMoved(func(evt teamspeak.ClientMovedEvent) { ... })
client.OnTextMessage(func(msg teamspeak.TextMessage) { ... })
client.OnPoked(func(evt teamspeak.PokeEvent) { ... })
client.OnKicked(func(reason string) { ... })
client.OnVoiceData(func(evt teamspeak.VoiceDataEvent) { ... })

Middleware

// 命令中间件:拦截/修改出站命令
client.UseCommandMiddleware(func(next func(string) error) func(string) error {
    return func(cmd string) error {
        log.Println("CMD:", cmd)
        return next(cmd)
    }
})

// 事件中间件:拦截/修改入站事件
client.UseEventMiddleware(func(next func(any)) func(any) {
    return func(evt any) {
        log.Println("EVT:", evt)
        next(evt)
    }
})

Local Patches

本项目通过 go.modreplace 指令使用本地补丁版本:

replace github.com/honeybbq/teamspeak-go => ./_patches/github.com/honeybbq/teamspeak-go

补丁修复了上游库的以下问题:

  • 32 位目标平台的整数溢出(math.MaxUint32 overflows int
  • 其他 gomobile 兼容性修复