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 测试断言
99 lines
4.4 KiB
Go
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
|
|
}
|