Files
cellbot/internal/adapter/onebot11/adapter.go

468 lines
12 KiB
Go
Raw Normal View History

package onebot11
import (
"context"
"fmt"
"sync"
"time"
"cellbot/internal/engine"
"cellbot/internal/protocol"
"cellbot/pkg/net"
"github.com/bytedance/sonic"
"go.uber.org/zap"
)
// Adapter OneBot11协议适配器
type Adapter struct {
config *Config
logger *zap.Logger
wsManager *net.WebSocketManager
httpClient *HTTPClient
wsWaiter *WSResponseWaiter
eventBus *engine.EventBus
selfID string
connected bool
mu sync.RWMutex
wsConnection *net.WebSocketConnection
ctx context.Context
cancel context.CancelFunc
}
// Config OneBot11配置
type Config struct {
// 连接配置
ConnectionType string `json:"connection_type"` // ws, ws-reverse, http, http-post
Host string `json:"host"`
Port int `json:"port"`
AccessToken string `json:"access_token"`
// WebSocket配置
WSUrl string `json:"ws_url"` // 正向WS地址
WSReverseUrl string `json:"ws_reverse_url"` // 反向WS监听地址
Heartbeat int `json:"heartbeat"` // 心跳间隔(秒)
ReconnectInterval int `json:"reconnect_interval"` // 重连间隔(秒)
// HTTP配置
HTTPUrl string `json:"http_url"` // 正向HTTP地址
HTTPPostUrl string `json:"http_post_url"` // HTTP POST上报地址
Secret string `json:"secret"` // 签名密钥
Timeout int `json:"timeout"` // 超时时间(秒)
// 其他配置
SelfID string `json:"self_id"` // 机器人QQ号
Nickname string `json:"nickname"` // 机器人昵称
}
// NewAdapter 创建OneBot11适配器
func NewAdapter(config *Config, logger *zap.Logger, wsManager *net.WebSocketManager, eventBus *engine.EventBus) *Adapter {
ctx, cancel := context.WithCancel(context.Background())
timeout := time.Duration(config.Timeout) * time.Second
if timeout == 0 {
timeout = 30 * time.Second
}
adapter := &Adapter{
config: config,
logger: logger.Named("onebot11"),
wsManager: wsManager,
wsWaiter: NewWSResponseWaiter(timeout, logger),
eventBus: eventBus,
selfID: config.SelfID,
ctx: ctx,
cancel: cancel,
}
// 如果使用HTTP连接初始化HTTP客户端
if config.ConnectionType == "http" && config.HTTPUrl != "" {
adapter.httpClient = NewHTTPClient(config.HTTPUrl, config.AccessToken, timeout, logger)
}
return adapter
}
// Name 获取协议名称
func (a *Adapter) Name() string {
return "OneBot"
}
// Version 获取协议版本
func (a *Adapter) Version() string {
return "11"
}
// Connect 建立连接
func (a *Adapter) Connect(ctx context.Context) error {
a.mu.Lock()
defer a.mu.Unlock()
if a.connected {
return fmt.Errorf("already connected")
}
a.logger.Info("Starting OneBot11 connection",
zap.String("connection_type", a.config.ConnectionType),
zap.String("self_id", a.selfID))
switch a.config.ConnectionType {
case "ws":
return a.connectWebSocket(ctx)
case "ws-reverse":
return a.connectWebSocketReverse(ctx)
case "http":
return a.connectHTTP(ctx)
case "http-post":
return a.connectHTTPPost(ctx)
default:
return fmt.Errorf("unsupported connection type: %s", a.config.ConnectionType)
}
}
// Disconnect 断开连接
func (a *Adapter) Disconnect(ctx context.Context) error {
a.mu.Lock()
defer a.mu.Unlock()
if !a.connected {
a.logger.Debug("Already disconnected, skipping")
return nil
}
a.logger.Info("Disconnecting OneBot11 adapter",
zap.String("connection_type", a.config.ConnectionType))
// 取消上下文
if a.cancel != nil {
a.cancel()
a.logger.Debug("Context cancelled")
}
// 关闭WebSocket连接
if a.wsConnection != nil {
a.logger.Info("Closing WebSocket connection",
zap.String("connection_id", a.wsConnection.ID))
a.wsManager.RemoveConnection(a.wsConnection.ID)
a.wsConnection = nil
}
// 关闭HTTP客户端
if a.httpClient != nil {
if err := a.httpClient.Close(); err != nil {
a.logger.Error("Failed to close HTTP client", zap.Error(err))
} else {
a.logger.Debug("HTTP client closed")
}
}
a.connected = false
a.logger.Info("OneBot11 adapter disconnected successfully")
return nil
}
// IsConnected 检查连接状态
func (a *Adapter) IsConnected() bool {
a.mu.RLock()
defer a.mu.RUnlock()
return a.connected
}
// GetSelfID 获取机器人自身ID
func (a *Adapter) GetSelfID() string {
return a.selfID
}
// SendAction 发送动作
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) {
// 序列化为OneBot11格式
data, err := a.SerializeAction(action)
if err != nil {
return nil, err
}
switch a.config.ConnectionType {
case "ws", "ws-reverse":
return a.sendActionWebSocket(data)
case "http":
return a.sendActionHTTP(data)
default:
return nil, fmt.Errorf("unsupported connection type for sending action: %s", a.config.ConnectionType)
}
}
// HandleEvent 处理事件
func (a *Adapter) HandleEvent(ctx context.Context, event protocol.Event) error {
a.logger.Debug("Handling event",
zap.String("type", string(event.GetType())),
zap.String("detail_type", event.GetDetailType()))
return nil
}
// ParseMessage 解析原始消息为Event
func (a *Adapter) ParseMessage(raw []byte) (protocol.Event, error) {
var rawEvent RawEvent
if err := sonic.Unmarshal(raw, &rawEvent); err != nil {
return nil, fmt.Errorf("failed to unmarshal raw event: %w", err)
}
return a.convertToEvent(&rawEvent)
}
// SerializeAction 序列化Action为协议格式
func (a *Adapter) SerializeAction(action protocol.Action) ([]byte, error) {
// 转换为OneBot11格式
ob11ActionName := ConvertAction(action)
// 检查是否有未转换的动作类型(如果转换后的名称与原始类型相同,说明没有匹配到)
originalType := string(action.GetType())
if ob11ActionName == originalType {
a.logger.Warn("Action type not converted, using original type",
zap.String("action_type", originalType),
zap.String("hint", "This action type may not be supported by OneBot11"))
}
ob11Action := &OB11Action{
Action: ob11ActionName,
Params: action.GetParams(),
}
return sonic.Marshal(ob11Action)
}
// connectWebSocket 正向WebSocket连接
func (a *Adapter) connectWebSocket(ctx context.Context) error {
if a.config.WSUrl == "" {
return fmt.Errorf("ws_url is required for ws connection")
}
a.logger.Info("Connecting to OneBot WebSocket server",
zap.String("url", a.config.WSUrl),
zap.Bool("has_token", a.config.AccessToken != ""))
// 添加访问令牌到URL
wsURL := a.config.WSUrl
if a.config.AccessToken != "" {
wsURL += "?access_token=" + a.config.AccessToken
a.logger.Debug("Added access token to WebSocket URL")
}
a.logger.Info("Dialing WebSocket...",
zap.String("full_url", wsURL))
wsConn, err := a.wsManager.Dial(wsURL, a.selfID)
if err != nil {
a.logger.Error("Failed to connect WebSocket",
zap.String("url", a.config.WSUrl),
zap.Error(err))
return fmt.Errorf("failed to connect websocket: %w", err)
}
a.wsConnection = wsConn
a.connected = true
a.logger.Info("WebSocket connected successfully",
zap.String("url", a.config.WSUrl),
zap.String("remote_addr", wsConn.RemoteAddr),
zap.String("connection_id", wsConn.ID))
// 启动消息接收处理
go a.handleWebSocketMessages()
a.logger.Info("WebSocket message handler started")
return nil
}
// connectWebSocketReverse 反向WebSocket连接
func (a *Adapter) connectWebSocketReverse(ctx context.Context) error {
// 反向WebSocket由客户端主动连接到服务器
// WebSocket服务器会在主Server中启动
// 这里只需要标记为已连接状态等待客户端通过HTTP服务器连接
a.connected = true
a.logger.Info("OneBot11 adapter ready for reverse WebSocket connections",
zap.String("bot_id", a.selfID),
zap.String("listen_addr", a.config.WSReverseUrl))
// 注意实际的WebSocket服务器由pkg/net/server.go提供
// OneBot客户端需要连接到 ws://host:port/ws?bot_id=<selfID>
return nil
}
// connectHTTP 正向HTTP连接
func (a *Adapter) connectHTTP(ctx context.Context) error {
if a.config.HTTPUrl == "" {
return fmt.Errorf("http_url is required for http connection")
}
// 创建HTTP客户端
// TODO: 实现HTTP轮询
a.connected = true
a.logger.Info("HTTP connected",
zap.String("url", a.config.HTTPUrl))
return nil
}
// connectHTTPPost HTTP POST上报
func (a *Adapter) connectHTTPPost(ctx context.Context) error {
if a.config.HTTPPostUrl == "" {
return fmt.Errorf("http_post_url is required for http-post connection")
}
// HTTP POST由客户端主动推送事件
a.connected = true
a.logger.Info("HTTP POST ready",
zap.String("url", a.config.HTTPPostUrl))
return nil
}
// sendActionWebSocket 通过WebSocket发送动作
func (a *Adapter) sendActionWebSocket(data []byte) (map[string]interface{}, error) {
if a.wsConnection == nil {
return nil, fmt.Errorf("websocket connection not established")
}
// 解析请求以获取或添加echo
var req OB11Action
if err := sonic.Unmarshal(data, &req); err != nil {
return nil, fmt.Errorf("failed to unmarshal action: %w", err)
}
// 如果没有echo生成一个
if req.Echo == "" {
req.Echo = GenerateEcho()
var err error
data, err = sonic.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal action with echo: %w", err)
}
}
// 发送消息
if err := a.wsConnection.SendMessage(data); err != nil {
return nil, fmt.Errorf("failed to send action: %w", err)
}
// 等待响应
resp, err := a.wsWaiter.Wait(req.Echo)
if err != nil {
return nil, err
}
// 检查响应状态
if resp.Status != "ok" && resp.Status != "async" {
return resp.Data, fmt.Errorf("action failed (retcode=%d)", resp.RetCode)
}
return resp.Data, nil
}
// sendActionHTTP 通过HTTP发送动作
func (a *Adapter) sendActionHTTP(data []byte) (map[string]interface{}, error) {
if a.httpClient == nil {
return nil, fmt.Errorf("http client not initialized")
}
// 解析请求
var req OB11Action
if err := sonic.Unmarshal(data, &req); err != nil {
return nil, fmt.Errorf("failed to unmarshal action: %w", err)
}
// 调用HTTP API
resp, err := a.httpClient.Call(a.ctx, req.Action, req.Params)
if err != nil {
return nil, err
}
// 检查响应状态
if resp.Status != "ok" && resp.Status != "async" {
return resp.Data, fmt.Errorf("action failed (retcode=%d)", resp.RetCode)
}
return resp.Data, nil
}
// handleWebSocketMessages 处理WebSocket消息
func (a *Adapter) handleWebSocketMessages() {
a.logger.Info("WebSocket message handler started, waiting for messages...")
for {
select {
case <-a.ctx.Done():
a.logger.Info("Context cancelled, stopping WebSocket message handler")
return
default:
}
if a.wsConnection == nil || a.wsConnection.Conn == nil {
a.logger.Warn("WebSocket connection is nil, stopping message handler")
return
}
// 读取消息
_, message, err := a.wsConnection.Conn.ReadMessage()
if err != nil {
a.logger.Error("Failed to read WebSocket message",
zap.Error(err),
zap.String("connection_id", a.wsConnection.ID))
return
}
a.logger.Debug("Received WebSocket message",
zap.Int("size", len(message)),
zap.String("preview", string(message[:min(len(message), 200)])))
// 尝试解析为响应
var resp OB11Response
if err := sonic.Unmarshal(message, &resp); err == nil {
// 如果有echo字段说明是API响应
if resp.Echo != "" {
a.logger.Debug("Received API response",
zap.String("echo", resp.Echo),
zap.String("status", resp.Status),
zap.Int("retcode", resp.RetCode))
a.wsWaiter.Notify(&resp)
continue
}
}
// 否则当作事件处理
a.logger.Info("Received OneBot event",
zap.ByteString("raw_event", message))
// 解析事件
a.logger.Info("Parsing OneBot event...")
event, err := a.ParseMessage(message)
if err != nil {
a.logger.Error("Failed to parse event",
zap.Error(err),
zap.ByteString("raw_message", message))
continue
}
// 发布事件到事件总线
a.logger.Info("Publishing event to event bus",
zap.String("event_type", string(event.GetType())),
zap.String("detail_type", event.GetDetailType()),
zap.String("self_id", event.GetSelfID()))
a.eventBus.Publish(event)
a.logger.Info("Event published successfully")
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}