package qq_obv11 // This file implements the server role of the OneBot v11 WebSocket channel: // the adapter listens on a TCP port and accepts inbound OneBot (反向) client // connections, validates any configured access token, and upgrades each HTTP // request to a WebSocket session. import ( "context" "errors" "fmt" "net" "net/http" "strings" ) // connectServer starts the HTTP listener in server mode. It returns once the // listener is bound; the session becomes live when a OneBot client connects. func (a *Adapter) connectServer(ctx context.Context) error { if a.cfg.ListenAddr == "" { return errors.New("qq-obv11: server mode requires listenAddr") } mux := http.NewServeMux() mux.HandleFunc("/", a.wsHandler) ln, err := net.Listen("tcp", a.cfg.ListenAddr) if err != nil { return fmt.Errorf("qq-obv11: listen %s: %w", a.cfg.ListenAddr, err) } a.mu.Lock() a.ln = ln a.srv = &http.Server{Handler: mux} a.mu.Unlock() go func() { if serveErr := a.srv.Serve(ln); serveErr != nil && serveErr != http.ErrServerClosed { a.log.Warn("websocket server stopped", "err", serveErr) } }() a.log.Info("websocket server listening (server mode)", "addr", ln.Addr().String()) return nil } // wsHandler is the HTTP handler that upgrades one inbound OneBot client // connection to a WebSocket session. func (a *Adapter) wsHandler(w http.ResponseWriter, r *http.Request) { if a.cfg.AccessToken != "" && !a.checkAuth(r) { http.Error(w, "Forbidden: invalid access_token", http.StatusForbidden) return } conn, err := upgradeWS(w, r) if err != nil { a.log.Warn("websocket upgrade failed", "err", err) return } a.setConn(conn) a.log.Info("onebot client connected (server mode)", "remote", r.RemoteAddr) } // checkAuth validates the access token carried by an upgrade request. OneBot // implementations pass it either as an access_token query parameter or as an // Authorization: Bearer header. func (a *Adapter) checkAuth(r *http.Request) bool { if got := r.URL.Query().Get("access_token"); got != "" { return got == a.cfg.AccessToken } auth := r.Header.Get("Authorization") if strings.HasPrefix(auth, "Bearer ") { return strings.TrimPrefix(auth, "Bearer ") == a.cfg.AccessToken } return false }