Files
Cyrene_v2/plugins/channels/kook/config.go
T
Aska 617157262b
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(channel): KOOK Webhook(WH) 接收模式——与 WS 配置切换(ReceiveMode)
- kook.Config 加 ReceiveMode("ws"默认/"webhook") + WebhookPath(默认/webhook/kook) + WebhookVerifyToken + WebhookEncryptKey(AES-256-CBC)
- 按权威文档(Reference/kaiheila webhook.md)实现: 无X-Signature HMAC, 真实校验=d.verify_token比对(不匹配403)+可选AES解密+zlib解压+challenge回显
- webhook.go: InboundHandler+WebhookPath, POST→zlib解压→AES解密→verify_token校验→challenge回显→归一UnifiedMessage→SetMessageHandler; WS模式Connect不拨网关仅标记connected
- channelbridge/webhookAdapter 接口改用 WebhookPath(); WebhookHandlers 收KOOK WH(ws模式跳过); 既有webhook通道补WebhookPath照常挂载
- namedAdapter 转发InboundHandler/WebhookPath; kook env兜底 +4个CHANNEL_KOOK_*键
- 测试: verify_token 403/challenge/zlib/AES往返/WebhookPath按模式/WH不拨gateway/WebhookHandlers收KOOK; 全仓build/vet/test绿
- 待真token端到端实测(需主人提供CHANNEL_KOOK_TOKEN+开发者后台切WebHook配回调URL)
2026-09-07 14:10:03 +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
// bearer-authenticated REST API.
//
// 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
}