Files
Cyrene_v2/plugins/channels/kook/config.go
T
Aska c1bd740f7c
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(kook): KOOK 机器人鉴权用 Authorization: Bot <token> 而非 Bearer
httpclient 硬编码 Bearer,导致 KOOK bot token 被当 OAuth2 用户令牌发,
/ gateway/index 返回 401『登录环境异常』。按 KOOK reference 鉴权规范,
给共享 httpclient 增加可配置 authScheme(默认 Bearer),KOOK 用 Bot。

- httpclient: 新增 WithAuthScheme 选项 + authScheme 字段,do() 按 scheme 组装
- kook/adapter: 装配时传 WithAuthScheme("Bot")
- 同步修正 config.go 注释与 send/identity 测试断言
2026-09-07 16:59:53 +08:00

99 lines
4.4 KiB
Go

// Package kook implements the built-in KOOK (KaiHeiLa) channel adapter for
// Cyrene v2. KOOK is a stateful channel with two mutually exclusive inbound
// modes: a WebSocket-gateway mode (events over a gateway WebSocket with
// hello/heartbeat/event/resume signaling) and a Webhook mode (KOOK POSTs event
// callbacks to a Cyrene gateway route). Outbound messages are always sent over a
// bot-token-authenticated REST API (`Authorization: Bot <token>`).
//
// The adapter conforms to internal/channel/adapter.PlatformAdapter, translating
// native KOOK events into the normalized adapter.UnifiedMessage model and
// delivering replies via /api/v3/message/create (channels) and
// /api/v3/direct-message/create (private chat). Identity is carried as channel
// actor metadata (author id + nickname) only — owner/member resolution is the
// channel layer's job, never a prompt-text heuristic.
//
// The design authority is DevDocs/design/08-渠道接入.md §4 (kook) and §7
// (framework reusability). It depends on internal/channel/adapter (including the
// shared wsutil and httpclient primitives), internal/logger, and the standard
// library. The bot token, verify token and encrypt key are never logged.
package kook
import (
"strings"
"time"
)
// Defaults and knobs for the KOOK integration.
const (
// defaultBaseURL is the KOOK REST API root.
defaultBaseURL = "https://www.kookapp.cn/api/v3"
// defaultHTTPTimeout bounds a single REST request.
defaultHTTPTimeout = 30 * time.Second
// gatewayPath is the gateway-address fetch endpoint relative to baseURL.
gatewayPath = "/gateway/index"
// maxMessageLen is KOOK's documented per-message content limit.
maxMessageLen = 8000
// heartbeatInterval is how often the client sends a ping (KOOK: 30s).
heartbeatInterval = 30 * time.Second
// helloTimeout bounds the wait for the handshake (hello/resume ack).
helloTimeout = 6 * time.Second
)
// Inbound receive modes. KOOK's webhook and websocket modes are mutually
// exclusive (DevDocs/design/08 §4): selecting webhook means the gateway WebSocket
// no longer delivers events, and vice versa. ReceiveMode decides which path the
// adapter uses; the two never run together.
const (
// modeWS is the default: connect the KOOK gateway WebSocket and receive events.
modeWS = "ws"
// modeWebhook exposes a Cyrene gateway route that KOOK POSTs callbacks to.
modeWebhook = "webhook"
// defaultWebhookPath is used when Config.WebhookPath is empty in webhook mode.
defaultWebhookPath = "/webhook/kook"
// defaultMaxBodyBytes caps an inbound webhook payload at 1 MiB.
defaultMaxBodyBytes = 1 << 20
)
// Config holds the settings required to reach the KOOK API.
type Config struct {
// BotToken is the secret bot token issued by the KOOK developer center. It is
// surfaced through Config() but never written to logs.
BotToken string `json:"bot_token"`
// BaseURL is the KOOK REST API root; empty uses the production default.
BaseURL string `json:"base_url"`
// ReceiveMode selects the inbound delivery path: "ws" (default) connects the
// KOOK gateway WebSocket; "webhook" exposes a gateway route that KOOK POSTs
// events to. The two are mutually exclusive.
ReceiveMode string `json:"receive_mode"`
// WebhookPath is the route the Cyrene gateway mounts the KOOK webhook handler
// at when ReceiveMode == "webhook". Empty uses defaultWebhookPath.
WebhookPath string `json:"webhook_path"`
// WebhookVerifyToken is the KOOK developer-center verify token. When set, every
// webhook callback's d.verify_token must match or it is rejected with 403.
WebhookVerifyToken string `json:"webhook_verify_token"`
// WebhookEncryptKey is the KOOK Encrypt Key. When set, an encrypted callback
// body ({"encrypt": ...}) is decrypted with AES-256-CBC before parsing.
WebhookEncryptKey string `json:"webhook_encrypt_key"`
}
// normalizedReceiveMode returns ReceiveMode, defaulting empty to "ws".
func (c Config) normalizedReceiveMode() string {
if strings.TrimSpace(c.ReceiveMode) == "" {
return modeWS
}
return strings.TrimSpace(c.ReceiveMode)
}
// IsWebhook reports whether the adapter should run in webhook mode.
func (c Config) IsWebhook() bool { return c.normalizedReceiveMode() == modeWebhook }
// Validate checks the configuration for values needed to connect. The bot token
// is required in both modes because outbound (reply) delivery always uses the
// bearer REST API.
func (c Config) Validate() error {
if strings.TrimSpace(c.BotToken) == "" {
return ErrMissingToken
}
return nil
}